ReactOS Fundraising Campaign 2012
 
€ 4,410 / € 30,000

Information | Donate

Home | Info | Community | Development | myReactOS | Contact Us

  1. Home
  2. Community
  3. Development
  4. myReactOS
  5. Fundraiser 2012

  1. Main Page
  2. Alphabetical List
  3. Data Structures
  4. Directories
  5. File List
  6. Data Fields
  7. Globals
  8. Related Pages

ReactOS Development > Doxygen

monthcal.c
Go to the documentation of this file.
00001 /*
00002  * Month calendar control
00003  *
00004  * Copyright 1998, 1999 Eric Kohl (ekohl@abo.rhein-zeitung.de)
00005  * Copyright 1999 Alex Priem (alexp@sci.kun.nl)
00006  * Copyright 1999 Chris Morgan <cmorgan@wpi.edu> and
00007  *        James Abbatiello <abbeyj@wpi.edu>
00008  * Copyright 2000 Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de>
00009  * Copyright 2009-2011 Nikolay Sivov
00010  *
00011  * This library is free software; you can redistribute it and/or
00012  * modify it under the terms of the GNU Lesser General Public
00013  * License as published by the Free Software Foundation; either
00014  * version 2.1 of the License, or (at your option) any later version.
00015  *
00016  * This library is distributed in the hope that it will be useful,
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00019  * Lesser General Public License for more details.
00020  *
00021  * You should have received a copy of the GNU Lesser General Public
00022  * License along with this library; if not, write to the Free Software
00023  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
00024  *
00025  * NOTE
00026  * 
00027  * This code was audited for completeness against the documented features
00028  * of Comctl32.dll version 6.0 on Oct. 20, 2004, by Dimitrie O. Paun.
00029  * 
00030  * Unless otherwise noted, we believe this code to be complete, as per
00031  * the specification mentioned above.
00032  * If you discover missing features, or bugs, please note them below.
00033  * 
00034  * TODO:
00035  *    -- MCM_[GS]ETUNICODEFORMAT
00036  *    -- handle resources better (doesn't work now); 
00037  *    -- take care of internationalization.
00038  *    -- keyboard handling.
00039  *    -- search for FIXME
00040  */
00041 
00042 #include <math.h>
00043 #include <stdarg.h>
00044 #include <stdio.h>
00045 #include <stdlib.h>
00046 #include <string.h>
00047 
00048 #include "windef.h"
00049 #include "winbase.h"
00050 #include "wingdi.h"
00051 #include "winuser.h"
00052 #include "winnls.h"
00053 #include "commctrl.h"
00054 #include "comctl32.h"
00055 #include "uxtheme.h"
00056 #include "vssym32.h"
00057 #include "wine/unicode.h"
00058 #include "wine/debug.h"
00059 
00060 WINE_DEFAULT_DEBUG_CHANNEL(monthcal);
00061 
00062 #define MC_SEL_LBUTUP       1   /* Left button released */
00063 #define MC_SEL_LBUTDOWN     2   /* Left button pressed in calendar */
00064 #define MC_PREVPRESSED      4   /* Prev month button pressed */
00065 #define MC_NEXTPRESSED      8   /* Next month button pressed */
00066 #define MC_PREVNEXTMONTHDELAY   350 /* when continuously pressing `next/prev
00067                        month', wait 350 ms before going
00068                        to the next/prev month */
00069 #define MC_TODAYUPDATEDELAY 120000 /* time between today check for update (2 min) */
00070 
00071 #define MC_PREVNEXTMONTHTIMER   1   /* Timer IDs */
00072 #define MC_TODAYUPDATETIMER     2
00073 
00074 #define MC_CALENDAR_PADDING     6
00075 
00076 #define countof(arr) (sizeof(arr)/sizeof(arr[0]))
00077 
00078 /* convert from days to 100 nanoseconds unit - used as FILETIME unit */
00079 #define DAYSTO100NSECS(days) (((ULONGLONG)(days))*24*60*60*10000000)
00080 
00081 enum CachedPen
00082 {
00083     PenRed = 0,
00084     PenText,
00085     PenLast
00086 };
00087 
00088 enum CachedBrush
00089 {
00090     BrushTitle = 0,
00091     BrushMonth,
00092     BrushBackground,
00093     BrushLast
00094 };
00095 
00096 /* single calendar data */
00097 typedef struct _CALENDAR_INFO
00098 {
00099     RECT title;      /* rect for the header above the calendar */
00100     RECT titlemonth; /* the 'month name' text in the header */
00101     RECT titleyear;  /* the 'year number' text in the header */
00102     RECT wdays;      /* week days at top */
00103     RECT days;       /* calendar area */
00104     RECT weeknums;   /* week numbers at left side */
00105 
00106     SYSTEMTIME month;/* contains calendar main month/year */
00107 } CALENDAR_INFO;
00108 
00109 typedef struct
00110 {
00111     HWND    hwndSelf;
00112     DWORD   dwStyle; /* cached GWL_STYLE */
00113 
00114     COLORREF    colors[MCSC_TRAILINGTEXT+1];
00115     HBRUSH      brushes[BrushLast];
00116     HPEN        pens[PenLast];
00117 
00118     HFONT   hFont;
00119     HFONT   hBoldFont;
00120     int     textHeight;
00121     int     textWidth;
00122     int     height_increment;
00123     int     width_increment;
00124     INT     delta;  /* scroll rate; # of months that the */
00125                         /* control moves when user clicks a scroll button */
00126     int     visible;    /* # of months visible */
00127     int     firstDay;   /* Start month calendar with firstDay's day,
00128                    stored in SYSTEMTIME format */
00129     BOOL    firstDaySet;    /* first week day differs from locale defined */
00130 
00131     BOOL    isUnicode;      /* value set with MCM_SETUNICODE format */
00132 
00133     MONTHDAYSTATE *monthdayState;
00134     SYSTEMTIME  todaysDate;
00135     BOOL    todaySet;       /* Today was forced with MCM_SETTODAY */
00136     int     status;     /* See MC_SEL flags */
00137     SYSTEMTIME  firstSel;   /* first selected day */
00138     INT     maxSelCount;
00139     SYSTEMTIME  minSel;         /* contains single selection when used without MCS_MULTISELECT */
00140     SYSTEMTIME  maxSel;
00141     SYSTEMTIME  focusedSel;     /* date currently focused with mouse movement */
00142     DWORD   rangeValid;
00143     SYSTEMTIME  minDate;
00144     SYSTEMTIME  maxDate;
00145 
00146     RECT titlebtnnext;  /* the `next month' button in the header */
00147     RECT titlebtnprev;  /* the `prev month' button in the header */
00148     RECT todayrect; /* `today: xx/xx/xx' text rect */
00149     HWND hwndNotify;    /* Window to receive the notifications */
00150     HWND hWndYearEdit;  /* Window Handle of edit box to handle years */
00151     HWND hWndYearUpDown;/* Window Handle of updown box to handle years */
00152     WNDPROC EditWndProc;  /* original Edit window procedure */
00153 
00154     CALENDAR_INFO *calendars;
00155     SIZE dim;           /* [cx,cy] - dimensions of calendars matrix, row/column count */
00156 } MONTHCAL_INFO, *LPMONTHCAL_INFO;
00157 
00158 static const WCHAR themeClass[] = { 'S','c','r','o','l','l','b','a','r',0 };
00159 
00160 /* empty SYSTEMTIME const */
00161 static const SYSTEMTIME st_null;
00162 /* valid date limits */
00163 static const SYSTEMTIME max_allowed_date = { /* wYear */ 9999, /* wMonth */ 12, /* wDayOfWeek */ 0, /* wDay */ 31 };
00164 static const SYSTEMTIME min_allowed_date = { /* wYear */ 1752, /* wMonth */ 9,  /* wDayOfWeek */ 0, /* wDay */ 14 };
00165 
00166 /* Prev/Next buttons */
00167 enum nav_direction
00168 {
00169     DIRECTION_BACKWARD,
00170     DIRECTION_FORWARD
00171 };
00172 
00173 /* helper functions  */
00174 static inline INT MONTHCAL_GetCalCount(const MONTHCAL_INFO *infoPtr)
00175 {
00176    return infoPtr->dim.cx * infoPtr->dim.cy;
00177 }
00178 
00179 /* send a single MCN_SELCHANGE notification */
00180 static inline void MONTHCAL_NotifySelectionChange(const MONTHCAL_INFO *infoPtr)
00181 {
00182     NMSELCHANGE nmsc;
00183 
00184     nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
00185     nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
00186     nmsc.nmhdr.code     = MCN_SELCHANGE;
00187     nmsc.stSelStart     = infoPtr->minSel;
00188     nmsc.stSelEnd       = infoPtr->maxSel;
00189     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
00190 }
00191 
00192 /* send a single MCN_SELECT notification */
00193 static inline void MONTHCAL_NotifySelect(const MONTHCAL_INFO *infoPtr)
00194 {
00195     NMSELCHANGE nmsc;
00196 
00197     nmsc.nmhdr.hwndFrom = infoPtr->hwndSelf;
00198     nmsc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
00199     nmsc.nmhdr.code     = MCN_SELECT;
00200     nmsc.stSelStart     = infoPtr->minSel;
00201     nmsc.stSelEnd       = infoPtr->maxSel;
00202 
00203     SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmsc.nmhdr.idFrom, (LPARAM)&nmsc);
00204 }
00205 
00206 static inline int MONTHCAL_MonthDiff(const SYSTEMTIME *left, const SYSTEMTIME *right)
00207 {
00208     return (right->wYear - left->wYear)*12 + right->wMonth - left->wMonth;
00209 }
00210 
00211 /* returns the number of days in any given month, checking for leap days */
00212 /* January is 1, December is 12 */
00213 int MONTHCAL_MonthLength(int month, int year)
00214 {
00215   const int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
00216   /* Wrap around, this eases handling. Getting length only we shouldn't care
00217      about year change here cause January and December have
00218      the same day quantity */
00219   if(month == 0)
00220     month = 12;
00221   else if(month == 13)
00222     month = 1;
00223 
00224   /* special case for calendar transition year */
00225   if(month == min_allowed_date.wMonth && year == min_allowed_date.wYear) return 19;
00226 
00227   /* if we have a leap year add 1 day to February */
00228   /* a leap year is a year either divisible by 400 */
00229   /* or divisible by 4 and not by 100 */
00230   if(month == 2) { /* February */
00231     return mdays[month - 1] + ((year%400 == 0) ? 1 : ((year%100 != 0) &&
00232      (year%4 == 0)) ? 1 : 0);
00233   }
00234   else {
00235     return mdays[month - 1];
00236   }
00237 }
00238 
00239 /* compares timestamps using date part only */
00240 static inline BOOL MONTHCAL_IsDateEqual(const SYSTEMTIME *first, const SYSTEMTIME *second)
00241 {
00242   return (first->wYear == second->wYear) && (first->wMonth == second->wMonth) &&
00243          (first->wDay  == second->wDay);
00244 }
00245 
00246 /* make sure that date fields are valid */
00247 static BOOL MONTHCAL_ValidateDate(const SYSTEMTIME *time)
00248 {
00249   if(time->wMonth < 1 || time->wMonth > 12 ) return FALSE;
00250   if(time->wDay > MONTHCAL_MonthLength(time->wMonth, time->wYear)) return FALSE;
00251 
00252   return TRUE;
00253 }
00254 
00255 /* Copies timestamp part only.
00256  *
00257  * PARAMETERS
00258  *
00259  *  [I] from : source date
00260  *  [O] to   : dest date
00261  */
00262 static void MONTHCAL_CopyTime(const SYSTEMTIME *from, SYSTEMTIME *to)
00263 {
00264   to->wHour   = from->wHour;
00265   to->wMinute = from->wMinute;
00266   to->wSecond = from->wSecond;
00267 }
00268 
00269 /* Copies date part only.
00270  *
00271  * PARAMETERS
00272  *
00273  *  [I] from : source date
00274  *  [O] to   : dest date
00275  */
00276 static void MONTHCAL_CopyDate(const SYSTEMTIME *from, SYSTEMTIME *to)
00277 {
00278   to->wYear  = from->wYear;
00279   to->wMonth = from->wMonth;
00280   to->wDay   = from->wDay;
00281   to->wDayOfWeek = from->wDayOfWeek;
00282 }
00283 
00284 /* Compares two dates in SYSTEMTIME format
00285  *
00286  * PARAMETERS
00287  *
00288  *  [I] first  : pointer to valid first date data to compare
00289  *  [I] second : pointer to valid second date data to compare
00290  *
00291  * RETURN VALUE
00292  *
00293  *  -1 : first <  second
00294  *   0 : first == second
00295  *   1 : first >  second
00296  *
00297  *  Note that no date validation performed, already validated values expected.
00298  */
00299 static LONG MONTHCAL_CompareSystemTime(const SYSTEMTIME *first, const SYSTEMTIME *second)
00300 {
00301   FILETIME ft_first, ft_second;
00302 
00303   SystemTimeToFileTime(first, &ft_first);
00304   SystemTimeToFileTime(second, &ft_second);
00305 
00306   return CompareFileTime(&ft_first, &ft_second);
00307 }
00308 
00309 static LONG MONTHCAL_CompareMonths(const SYSTEMTIME *first, const SYSTEMTIME *second)
00310 {
00311   SYSTEMTIME st_first, st_second;
00312 
00313   st_first = st_second = st_null;
00314   MONTHCAL_CopyDate(first, &st_first);
00315   MONTHCAL_CopyDate(second, &st_second);
00316   st_first.wDay = st_second.wDay = 1;
00317 
00318   return MONTHCAL_CompareSystemTime(&st_first, &st_second);
00319 }
00320 
00321 static LONG MONTHCAL_CompareDate(const SYSTEMTIME *first, const SYSTEMTIME *second)
00322 {
00323   SYSTEMTIME st_first, st_second;
00324 
00325   st_first = st_second = st_null;
00326   MONTHCAL_CopyDate(first, &st_first);
00327   MONTHCAL_CopyDate(second, &st_second);
00328 
00329   return MONTHCAL_CompareSystemTime(&st_first, &st_second);
00330 }
00331 
00332 /* Checks largest possible date range and configured one
00333  *
00334  * PARAMETERS
00335  *
00336  *  [I] infoPtr : valid pointer to control data
00337  *  [I] date    : pointer to valid date data to check
00338  *  [I] fix     : make date fit valid range
00339  *
00340  * RETURN VALUE
00341  *
00342  *  TRUE  - date within largest and configured range
00343  *  FALSE - date is outside largest or configured range
00344  */
00345 static BOOL MONTHCAL_IsDateInValidRange(const MONTHCAL_INFO *infoPtr,
00346                                         SYSTEMTIME *date, BOOL fix)
00347 {
00348   const SYSTEMTIME *fix_st = NULL;
00349 
00350   if(MONTHCAL_CompareSystemTime(date, &max_allowed_date) == 1) {
00351      fix_st = &max_allowed_date;
00352   }
00353   else if(MONTHCAL_CompareSystemTime(date, &min_allowed_date) == -1) {
00354      fix_st = &min_allowed_date;
00355   }
00356   else if(infoPtr->rangeValid & GDTR_MAX) {
00357      if((MONTHCAL_CompareSystemTime(date, &infoPtr->maxDate) == 1)) {
00358        fix_st = &infoPtr->maxDate;
00359      }
00360   }
00361   else if(infoPtr->rangeValid & GDTR_MIN) {
00362      if((MONTHCAL_CompareSystemTime(date, &infoPtr->minDate) == -1)) {
00363        fix_st = &infoPtr->minDate;
00364      }
00365   }
00366 
00367   if (fix && fix_st) {
00368     date->wYear  = fix_st->wYear;
00369     date->wMonth = fix_st->wMonth;
00370   }
00371 
00372   return fix_st ? FALSE : TRUE;
00373 }
00374 
00375 /* Checks passed range width with configured maximum selection count
00376  *
00377  * PARAMETERS
00378  *
00379  *  [I] infoPtr : valid pointer to control data
00380  *  [I] range0  : pointer to valid date data (requested bound)
00381  *  [I] range1  : pointer to valid date data (primary bound)
00382  *  [O] adjust  : returns adjusted range bound to fit maximum range (optional)
00383  *
00384  *  Adjust value computed basing on primary bound and current maximum selection
00385  *  count. For simple range check (without adjusted value required) (range0, range1)
00386  *  relation means nothing.
00387  *
00388  * RETURN VALUE
00389  *
00390  *  TRUE  - range is shorter or equal to maximum
00391  *  FALSE - range is larger than maximum
00392  */
00393 static BOOL MONTHCAL_IsSelRangeValid(const MONTHCAL_INFO *infoPtr,
00394                                      const SYSTEMTIME *range0,
00395                                      const SYSTEMTIME *range1,
00396                                      SYSTEMTIME *adjust)
00397 {
00398   ULARGE_INTEGER ul_range0, ul_range1, ul_diff;
00399   FILETIME ft_range0, ft_range1;
00400   LONG cmp;
00401 
00402   SystemTimeToFileTime(range0, &ft_range0);
00403   SystemTimeToFileTime(range1, &ft_range1);
00404 
00405   ul_range0.u.LowPart  = ft_range0.dwLowDateTime;
00406   ul_range0.u.HighPart = ft_range0.dwHighDateTime;
00407   ul_range1.u.LowPart  = ft_range1.dwLowDateTime;
00408   ul_range1.u.HighPart = ft_range1.dwHighDateTime;
00409 
00410   cmp = CompareFileTime(&ft_range0, &ft_range1);
00411 
00412   if(cmp == 1)
00413      ul_diff.QuadPart = ul_range0.QuadPart - ul_range1.QuadPart;
00414   else
00415      ul_diff.QuadPart = -ul_range0.QuadPart + ul_range1.QuadPart;
00416 
00417   if(ul_diff.QuadPart >= DAYSTO100NSECS(infoPtr->maxSelCount)) {
00418 
00419      if(adjust) {
00420        if(cmp == 1)
00421           ul_range0.QuadPart = ul_range1.QuadPart + DAYSTO100NSECS(infoPtr->maxSelCount - 1);
00422        else
00423           ul_range0.QuadPart = ul_range1.QuadPart - DAYSTO100NSECS(infoPtr->maxSelCount - 1);
00424 
00425        ft_range0.dwLowDateTime  = ul_range0.u.LowPart;
00426        ft_range0.dwHighDateTime = ul_range0.u.HighPart;
00427        FileTimeToSystemTime(&ft_range0, adjust);
00428      }
00429 
00430      return FALSE;
00431   }
00432   else return TRUE;
00433 }
00434 
00435 /* Used in MCM_SETRANGE/MCM_SETSELRANGE to determine resulting time part.
00436    Milliseconds are intentionally not validated. */
00437 static BOOL MONTHCAL_ValidateTime(const SYSTEMTIME *time)
00438 {
00439   if((time->wHour > 24) || (time->wMinute > 59) || (time->wSecond > 59))
00440     return FALSE;
00441   else
00442     return TRUE;
00443 }
00444 
00445 /* Note:Depending on DST, this may be offset by a day.
00446    Need to find out if we're on a DST place & adjust the clock accordingly.
00447    Above function assumes we have a valid data.
00448    Valid for year>1752;  1 <= d <= 31, 1 <= m <= 12.
00449    0 = Sunday.
00450 */
00451 
00452 /* Returns the day in the week
00453  *
00454  * PARAMETERS
00455  *  [i] date    : input date
00456  *  [I] inplace : set calculated value back to date structure
00457  *
00458  * RETURN VALUE
00459  *   day of week in SYSTEMTIME format: (0 == sunday,..., 6 == saturday)
00460  */
00461 int MONTHCAL_CalculateDayOfWeek(SYSTEMTIME *date, BOOL inplace)
00462 {
00463   SYSTEMTIME st = st_null;
00464   FILETIME ft;
00465 
00466   MONTHCAL_CopyDate(date, &st);
00467 
00468   SystemTimeToFileTime(&st, &ft);
00469   FileTimeToSystemTime(&ft, &st);
00470 
00471   if (inplace) date->wDayOfWeek = st.wDayOfWeek;
00472 
00473   return st.wDayOfWeek;
00474 }
00475 
00476 /* add/subtract 'months' from date */
00477 static inline void MONTHCAL_GetMonth(SYSTEMTIME *date, INT months)
00478 {
00479   INT length, m = date->wMonth + months;
00480 
00481   date->wYear += m > 0 ? (m - 1) / 12 : m / 12 - 1;
00482   date->wMonth = m > 0 ? (m - 1) % 12 + 1 : 12 + m % 12;
00483   /* fix moving from last day in a month */
00484   length = MONTHCAL_MonthLength(date->wMonth, date->wYear);
00485   if(date->wDay > length) date->wDay = length;
00486   MONTHCAL_CalculateDayOfWeek(date, TRUE);
00487 }
00488 
00489 /* properly updates date to point on next month */
00490 static inline void MONTHCAL_GetNextMonth(SYSTEMTIME *date)
00491 {
00492   MONTHCAL_GetMonth(date, 1);
00493 }
00494 
00495 /* properly updates date to point on prev month */
00496 static inline void MONTHCAL_GetPrevMonth(SYSTEMTIME *date)
00497 {
00498   MONTHCAL_GetMonth(date, -1);
00499 }
00500 
00501 /* Returns full date for a first currently visible day */
00502 static void MONTHCAL_GetMinDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date)
00503 {
00504   /* zero indexed calendar has the earliest date */
00505   SYSTEMTIME st_first = infoPtr->calendars[0].month;
00506   INT firstDay;
00507 
00508   st_first.wDay = 1;
00509   firstDay = MONTHCAL_CalculateDayOfWeek(&st_first, FALSE);
00510 
00511   *date = infoPtr->calendars[0].month;
00512   MONTHCAL_GetPrevMonth(date);
00513 
00514   date->wDay = MONTHCAL_MonthLength(date->wMonth, date->wYear) +
00515                (infoPtr->firstDay - firstDay) % 7 + 1;
00516 
00517   if(date->wDay > MONTHCAL_MonthLength(date->wMonth, date->wYear))
00518     date->wDay -= 7;
00519 
00520   /* fix day of week */
00521   MONTHCAL_CalculateDayOfWeek(date, TRUE);
00522 }
00523 
00524 /* Returns full date for a last currently visible day */
00525 static void MONTHCAL_GetMaxDate(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *date)
00526 {
00527   /* the latest date is in latest calendar */
00528   SYSTEMTIME st, *lt_month = &infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
00529   INT first_day;
00530 
00531   *date = *lt_month;
00532   st = *lt_month;
00533 
00534   /* day of week of first day of current month */
00535   st.wDay = 1;
00536   first_day = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
00537 
00538   MONTHCAL_GetNextMonth(date);
00539   MONTHCAL_GetPrevMonth(&st);
00540 
00541   /* last calendar starts with some date from previous month that not displayed */
00542   st.wDay = MONTHCAL_MonthLength(st.wMonth, st.wYear) +
00543              (infoPtr->firstDay - first_day) % 7 + 1;
00544   if (st.wDay > MONTHCAL_MonthLength(st.wMonth, st.wYear)) st.wDay -= 7;
00545 
00546   /* Use month length to get max day. 42 means max day count in calendar area */
00547   date->wDay = 42 - (MONTHCAL_MonthLength(st.wMonth, st.wYear) - st.wDay + 1) -
00548                      MONTHCAL_MonthLength(lt_month->wMonth, lt_month->wYear);
00549 
00550   /* fix day of week */
00551   MONTHCAL_CalculateDayOfWeek(date, TRUE);
00552 }
00553 
00554 /* From a given point calculate the row, column and day in the calendar,
00555    'day == 0' means the last day of the last month. */
00556 static int MONTHCAL_GetDayFromPos(const MONTHCAL_INFO *infoPtr, POINT pt, INT calIdx)
00557 {
00558   SYSTEMTIME st = infoPtr->calendars[calIdx].month;
00559   int firstDay, col, row;
00560   RECT client;
00561 
00562   GetClientRect(infoPtr->hwndSelf, &client);
00563 
00564   /* if the point is outside the x bounds of the window put it at the boundary */
00565   if (pt.x > client.right) pt.x = client.right;
00566 
00567   col = (pt.x - infoPtr->calendars[calIdx].days.left ) / infoPtr->width_increment;
00568   row = (pt.y - infoPtr->calendars[calIdx].days.top  ) / infoPtr->height_increment;
00569 
00570   st.wDay = 1;
00571   firstDay = (MONTHCAL_CalculateDayOfWeek(&st, FALSE) + 6 - infoPtr->firstDay) % 7;
00572   return col + 7 * row - firstDay;
00573 }
00574 
00575 /* Get day position for given date and calendar
00576  *
00577  * PARAMETERS
00578  *
00579  *  [I] infoPtr : pointer to control data
00580  *  [I] date : date value
00581  *  [O] col : day column (zero based)
00582  *  [O] row : week column (zero based)
00583  *  [I] calIdx : calendar index
00584  */
00585 static void MONTHCAL_GetDayPos(const MONTHCAL_INFO *infoPtr, const SYSTEMTIME *date,
00586     INT *col, INT *row, INT calIdx)
00587 {
00588   SYSTEMTIME st = infoPtr->calendars[calIdx].month;
00589   INT first;
00590 
00591   st.wDay = 1;
00592   first = (MONTHCAL_CalculateDayOfWeek(&st, FALSE) + 6 - infoPtr->firstDay) % 7;
00593 
00594   if (calIdx == 0 || calIdx == MONTHCAL_GetCalCount(infoPtr)-1) {
00595       const SYSTEMTIME *cal = &infoPtr->calendars[calIdx].month;
00596       LONG cmp = MONTHCAL_CompareMonths(date, &st);
00597 
00598       /* previous month */
00599       if (cmp == -1) {
00600         *col = (first - MONTHCAL_MonthLength(date->wMonth, cal->wYear) + date->wDay) % 7;
00601         *row = 0;
00602         return;
00603       }
00604 
00605       /* next month calculation is same as for current, just add current month length */
00606       if (cmp == 1)
00607           first += MONTHCAL_MonthLength(cal->wMonth, cal->wYear);
00608   }
00609 
00610   *col = (date->wDay + first) % 7;
00611   *row = (date->wDay + first - *col) / 7;
00612 }
00613 
00614 /* returns bounding box for day in given position in given calendar */
00615 static inline void MONTHCAL_GetDayRectI(const MONTHCAL_INFO *infoPtr, RECT *r,
00616   INT col, INT row, INT calIdx)
00617 {
00618   r->left   = infoPtr->calendars[calIdx].days.left + col * infoPtr->width_increment;
00619   r->right  = r->left + infoPtr->width_increment;
00620   r->top    = infoPtr->calendars[calIdx].days.top  + row * infoPtr->height_increment;
00621   r->bottom = r->top + infoPtr->textHeight;
00622 }
00623 
00624 /* Returns bounding box for given date
00625  *
00626  * NOTE: when calendar index is unknown pass -1
00627  */
00628 static inline void MONTHCAL_GetDayRect(const MONTHCAL_INFO *infoPtr, const SYSTEMTIME *date,
00629     RECT *r, INT calIdx)
00630 {
00631   INT col, row;
00632 
00633   if (calIdx == -1)
00634   {
00635       INT cmp = MONTHCAL_CompareMonths(date, &infoPtr->calendars[0].month);
00636 
00637       if (cmp <= 0)
00638           calIdx = 0;
00639       else
00640       {
00641           cmp = MONTHCAL_CompareMonths(date, &infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month);
00642           if (cmp >= 0)
00643               calIdx = MONTHCAL_GetCalCount(infoPtr)-1;
00644           else
00645           {
00646               for (calIdx = 1; calIdx < MONTHCAL_GetCalCount(infoPtr)-1; calIdx++)
00647                   if (MONTHCAL_CompareMonths(date, &infoPtr->calendars[calIdx].month) == 0)
00648                       break;
00649           }
00650       }
00651   }
00652 
00653   MONTHCAL_GetDayPos(infoPtr, date, &col, &row, calIdx);
00654   MONTHCAL_GetDayRectI(infoPtr, r, col, row, calIdx);
00655 }
00656 
00657 static LRESULT
00658 MONTHCAL_GetMonthRange(const MONTHCAL_INFO *infoPtr, DWORD flag, SYSTEMTIME *st)
00659 {
00660   INT range;
00661 
00662   TRACE("flag=%d, st=%p\n", flag, st);
00663 
00664   switch (flag) {
00665   case GMR_VISIBLE:
00666   {
00667       if (st)
00668       {
00669           st[0] = infoPtr->calendars[0].month;
00670           st[1] = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
00671 
00672           if (st[0].wMonth == min_allowed_date.wMonth &&
00673               st[0].wYear  == min_allowed_date.wYear)
00674           {
00675               st[0].wDay = min_allowed_date.wDay;
00676           }
00677           else
00678               st[0].wDay = 1;
00679           MONTHCAL_CalculateDayOfWeek(&st[0], TRUE);
00680 
00681           st[1].wDay = MONTHCAL_MonthLength(st[1].wMonth, st[1].wYear);
00682           MONTHCAL_CalculateDayOfWeek(&st[1], TRUE);
00683       }
00684 
00685       range = MONTHCAL_GetCalCount(infoPtr);
00686       break;
00687   }
00688   case GMR_DAYSTATE:
00689   {
00690       if (st)
00691       {
00692           MONTHCAL_GetMinDate(infoPtr, &st[0]);
00693           MONTHCAL_GetMaxDate(infoPtr, &st[1]);
00694       }
00695       /* include two partially visible months */
00696       range = MONTHCAL_GetCalCount(infoPtr) + 2;
00697       break;
00698   }
00699   default:
00700       WARN("Unknown flag value, got %d\n", flag);
00701       range = 0;
00702   }
00703 
00704   return range;
00705 }
00706 
00707 /* Focused day helper:
00708 
00709    - set focused date to given value;
00710    - reset to zero value if NULL passed;
00711    - invalidate previous and new day rectangle only if needed.
00712 
00713    Returns TRUE if focused day changed, FALSE otherwise.
00714 */
00715 static BOOL MONTHCAL_SetDayFocus(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *st)
00716 {
00717   RECT r;
00718 
00719   if(st)
00720   {
00721     /* there's nothing to do if it's the same date,
00722        mouse move within same date rectangle case */
00723     if(MONTHCAL_IsDateEqual(&infoPtr->focusedSel, st)) return FALSE;
00724 
00725     /* invalidate old focused day */
00726     MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1);
00727     InvalidateRect(infoPtr->hwndSelf, &r, FALSE);
00728 
00729     infoPtr->focusedSel = *st;
00730   }
00731 
00732   MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1);
00733 
00734   if(!st && MONTHCAL_ValidateDate(&infoPtr->focusedSel))
00735     infoPtr->focusedSel = st_null;
00736 
00737   /* on set invalidates new day, on reset clears previous focused day */
00738   InvalidateRect(infoPtr->hwndSelf, &r, FALSE);
00739 
00740   return TRUE;
00741 }
00742 
00743 /* draw today boundary box for specified rectangle */
00744 static void MONTHCAL_Circle(const MONTHCAL_INFO *infoPtr, HDC hdc, const RECT *r)
00745 {
00746   HPEN old_pen = SelectObject(hdc, infoPtr->pens[PenRed]);
00747   HBRUSH old_brush;
00748 
00749   old_brush = SelectObject(hdc, GetStockObject(NULL_BRUSH));
00750   Rectangle(hdc, r->left, r->top, r->right, r->bottom);
00751 
00752   SelectObject(hdc, old_brush);
00753   SelectObject(hdc, old_pen);
00754 }
00755 
00756 /* Draw today day mark rectangle
00757  *
00758  * [I] hdc  : context to draw in
00759  * [I] date : day to mark with rectangle
00760  *
00761  */
00762 static void MONTHCAL_CircleDay(const MONTHCAL_INFO *infoPtr, HDC hdc,
00763                                const SYSTEMTIME *date)
00764 {
00765   RECT r;
00766 
00767   MONTHCAL_GetDayRect(infoPtr, date, &r, -1);
00768   MONTHCAL_Circle(infoPtr, hdc, &r);
00769 }
00770 
00771 static void MONTHCAL_DrawDay(const MONTHCAL_INFO *infoPtr, HDC hdc, const SYSTEMTIME *st,
00772                              int bold, const PAINTSTRUCT *ps)
00773 {
00774   static const WCHAR fmtW[] = { '%','d',0 };
00775   WCHAR buf[10];
00776   RECT r, r_temp;
00777   COLORREF oldCol = 0;
00778   COLORREF oldBk  = 0;
00779   INT old_bkmode, selection;
00780 
00781   /* no need to check styles: when selection is not valid, it is set to zero.
00782      1 < day < 31, so everything is OK */
00783   MONTHCAL_GetDayRect(infoPtr, st, &r, -1);
00784   if(!IntersectRect(&r_temp, &(ps->rcPaint), &r)) return;
00785 
00786   if ((MONTHCAL_CompareDate(st, &infoPtr->minSel) >= 0) &&
00787       (MONTHCAL_CompareDate(st, &infoPtr->maxSel) <= 0))
00788   {
00789     TRACE("%d %d %d\n", st->wDay, infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
00790     TRACE("%s\n", wine_dbgstr_rect(&r));
00791     oldCol = SetTextColor(hdc, infoPtr->colors[MCSC_MONTHBK]);
00792     oldBk = SetBkColor(hdc, infoPtr->colors[MCSC_TRAILINGTEXT]);
00793     FillRect(hdc, &r, infoPtr->brushes[BrushTitle]);
00794 
00795     selection = 1;
00796   }
00797   else
00798     selection = 0;
00799 
00800   SelectObject(hdc, bold ? infoPtr->hBoldFont : infoPtr->hFont);
00801 
00802   old_bkmode = SetBkMode(hdc, TRANSPARENT);
00803   wsprintfW(buf, fmtW, st->wDay);
00804   DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE );
00805   SetBkMode(hdc, old_bkmode);
00806 
00807   if (selection)
00808   {
00809     SetTextColor(hdc, oldCol);
00810     SetBkColor(hdc, oldBk);
00811   }
00812 }
00813 
00814 static void MONTHCAL_PaintButton(MONTHCAL_INFO *infoPtr, HDC hdc, enum nav_direction button)
00815 {
00816     HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
00817     RECT *r = button == DIRECTION_FORWARD ? &infoPtr->titlebtnnext : &infoPtr->titlebtnprev;
00818     BOOL pressed = button == DIRECTION_FORWARD ? infoPtr->status & MC_NEXTPRESSED :
00819                                                  infoPtr->status & MC_PREVPRESSED;
00820     if (theme)
00821     {
00822         static const int states[] = {
00823             /* Prev button */
00824             ABS_LEFTNORMAL,  ABS_LEFTPRESSED,  ABS_LEFTDISABLED,
00825             /* Next button */
00826             ABS_RIGHTNORMAL, ABS_RIGHTPRESSED, ABS_RIGHTDISABLED
00827         };
00828         int stateNum = button == DIRECTION_FORWARD ? 3 : 0;
00829         if (pressed)
00830             stateNum += 1;
00831         else
00832         {
00833             if (infoPtr->dwStyle & WS_DISABLED) stateNum += 2;
00834         }
00835         DrawThemeBackground (theme, hdc, SBP_ARROWBTN, states[stateNum], r, NULL);
00836     }
00837     else
00838     {
00839         int style = button == DIRECTION_FORWARD ? DFCS_SCROLLRIGHT : DFCS_SCROLLLEFT;
00840         if (pressed)
00841             style |= DFCS_PUSHED;
00842         else
00843         {
00844             if (infoPtr->dwStyle & WS_DISABLED) style |= DFCS_INACTIVE;
00845         }
00846         
00847         DrawFrameControl(hdc, r, DFC_SCROLL, style);
00848     }
00849 }
00850 
00851 /* paint a title with buttons and month/year string */
00852 static void MONTHCAL_PaintTitle(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
00853 {
00854   static const WCHAR fmt_monthW[] = { '%','s',' ','%','l','d',0 };
00855   RECT *title = &infoPtr->calendars[calIdx].title;
00856   const SYSTEMTIME *st = &infoPtr->calendars[calIdx].month;
00857   WCHAR buf_month[80], buf_fmt[80];
00858   SIZE sz;
00859 
00860   /* fill header box */
00861   FillRect(hdc, title, infoPtr->brushes[BrushTitle]);
00862 
00863   /* month/year string */
00864   SetBkColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
00865   SetTextColor(hdc, infoPtr->colors[MCSC_TITLETEXT]);
00866   SelectObject(hdc, infoPtr->hBoldFont);
00867 
00868   GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1 + st->wMonth - 1,
00869                  buf_month, countof(buf_month));
00870 
00871   wsprintfW(buf_fmt, fmt_monthW, buf_month, st->wYear);
00872   DrawTextW(hdc, buf_fmt, strlenW(buf_fmt), title,
00873                       DT_CENTER | DT_VCENTER | DT_SINGLELINE);
00874 
00875   /* update title rectangles with current month - used while testing hits */
00876   GetTextExtentPoint32W(hdc, buf_fmt, strlenW(buf_fmt), &sz);
00877   infoPtr->calendars[calIdx].titlemonth.left = title->right / 2 + title->left / 2 - sz.cx / 2;
00878   infoPtr->calendars[calIdx].titleyear.right = title->right / 2 + title->left / 2 + sz.cx / 2;
00879 
00880   GetTextExtentPoint32W(hdc, buf_month, strlenW(buf_month), &sz);
00881   infoPtr->calendars[calIdx].titlemonth.right = infoPtr->calendars[calIdx].titlemonth.left + sz.cx;
00882   infoPtr->calendars[calIdx].titleyear.left   = infoPtr->calendars[calIdx].titlemonth.right;
00883 }
00884 
00885 static void MONTHCAL_PaintWeeknumbers(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
00886 {
00887   const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month;
00888   static const WCHAR fmt_weekW[] = { '%','d',0 };
00889   INT mindays, weeknum, weeknum1, startofprescal;
00890   INT i, prev_month;
00891   SYSTEMTIME st;
00892   WCHAR buf[80];
00893   HPEN old_pen;
00894   RECT r;
00895 
00896   if (!(infoPtr->dwStyle & MCS_WEEKNUMBERS)) return;
00897 
00898   MONTHCAL_GetMinDate(infoPtr, &st);
00899   startofprescal = st.wDay;
00900   st = *date;
00901 
00902   prev_month = date->wMonth - 1;
00903   if(prev_month == 0) prev_month = 12;
00904 
00905   /*
00906      Rules what week to call the first week of a new year:
00907      LOCALE_IFIRSTWEEKOFYEAR == 0 (e.g US?):
00908      The week containing Jan 1 is the first week of year
00909      LOCALE_IFIRSTWEEKOFYEAR == 2 (e.g. Germany):
00910      First week of year must contain 4 days of the new year
00911      LOCALE_IFIRSTWEEKOFYEAR == 1  (what countries?)
00912      The first week of the year must contain only days of the new year
00913   */
00914   GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, buf, countof(buf));
00915   weeknum = atoiW(buf);
00916   switch (weeknum)
00917   {
00918     case 1: mindays = 6;
00919     break;
00920     case 2: mindays = 3;
00921     break;
00922     case 0: mindays = 0;
00923         break;
00924     default:
00925         WARN("Unknown LOCALE_IFIRSTWEEKOFYEAR value %d, defaulting to 0\n", weeknum);
00926     mindays = 0;
00927   }
00928 
00929   if (date->wMonth == 1)
00930   {
00931     /* calculate all those exceptions for January */
00932     st.wDay = st.wMonth = 1;
00933     weeknum1 = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
00934     if ((infoPtr->firstDay - weeknum1) % 7 > mindays)
00935     weeknum = 1;
00936     else
00937     {
00938     weeknum = 0;
00939     for(i = 0; i < 11; i++)
00940        weeknum += MONTHCAL_MonthLength(i+1, date->wYear - 1);
00941 
00942     weeknum  += startofprescal + 7;
00943     weeknum  /= 7;
00944     st.wYear -= 1;
00945     weeknum1  = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
00946     if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++;
00947     }
00948   }
00949   else
00950   {
00951     weeknum = 0;
00952     for(i = 0; i < prev_month - 1; i++)
00953     weeknum += MONTHCAL_MonthLength(i+1, date->wYear);
00954 
00955     weeknum += startofprescal + 7;
00956     weeknum /= 7;
00957     st.wDay = st.wMonth = 1;
00958     weeknum1 = MONTHCAL_CalculateDayOfWeek(&st, FALSE);
00959     if ((infoPtr->firstDay - weeknum1) % 7 > mindays) weeknum++;
00960   }
00961 
00962   r = infoPtr->calendars[calIdx].weeknums;
00963 
00964   /* erase whole week numbers area */
00965   FillRect(hdc, &r, infoPtr->brushes[BrushMonth]);
00966   SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
00967 
00968   /* reduce rectangle to one week number */
00969   r.bottom = r.top + infoPtr->height_increment;
00970 
00971   for(i = 0; i < 6; i++) {
00972     if((i == 0) && (weeknum > 50))
00973     {
00974         wsprintfW(buf, fmt_weekW, weeknum);
00975         weeknum = 0;
00976     }
00977     else if((i == 5) && (weeknum > 47))
00978     {
00979     wsprintfW(buf, fmt_weekW, 1);
00980     }
00981     else
00982     wsprintfW(buf, fmt_weekW, weeknum + i);
00983 
00984     DrawTextW(hdc, buf, -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
00985     OffsetRect(&r, 0, infoPtr->height_increment);
00986   }
00987 
00988   /* line separator for week numbers column */
00989   old_pen = SelectObject(hdc, infoPtr->pens[PenText]);
00990   MoveToEx(hdc, infoPtr->calendars[calIdx].weeknums.right, infoPtr->calendars[calIdx].weeknums.top + 3 , NULL);
00991   LineTo(hdc,   infoPtr->calendars[calIdx].weeknums.right, infoPtr->calendars[calIdx].weeknums.bottom);
00992   SelectObject(hdc, old_pen);
00993 }
00994 
00995 /* bottom today date */
00996 static void MONTHCAL_PaintTodayTitle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
00997 {
00998   static const WCHAR fmt_todayW[] = { '%','s',' ','%','s',0 };
00999   WCHAR buf_todayW[30], buf_dateW[20], buf[80];
01000   RECT text_rect, box_rect;
01001   HFONT old_font;
01002   INT col;
01003 
01004   if(infoPtr->dwStyle & MCS_NOTODAY) return;
01005 
01006   if (!LoadStringW(COMCTL32_hModule, IDM_TODAY, buf_todayW, countof(buf_todayW)))
01007   {
01008     static const WCHAR todayW[] = { 'T','o','d','a','y',':',0 };
01009     WARN("Can't load resource\n");
01010     strcpyW(buf_todayW, todayW);
01011   }
01012 
01013   col = infoPtr->dwStyle & MCS_NOTODAYCIRCLE ? 0 : 1;
01014   if (infoPtr->dwStyle & MCS_WEEKNUMBERS) col--;
01015   /* label is located below first calendar last row */
01016   MONTHCAL_GetDayRectI(infoPtr, &text_rect, col, 6, infoPtr->dim.cx * infoPtr->dim.cy - infoPtr->dim.cx);
01017   box_rect = text_rect;
01018 
01019   GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &infoPtr->todaysDate, NULL,
01020                                                       buf_dateW, countof(buf_dateW));
01021   old_font = SelectObject(hdc, infoPtr->hBoldFont);
01022   SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]);
01023 
01024   wsprintfW(buf, fmt_todayW, buf_todayW, buf_dateW);
01025   DrawTextW(hdc, buf, -1, &text_rect, DT_CALCRECT | DT_LEFT | DT_VCENTER | DT_SINGLELINE);
01026   DrawTextW(hdc, buf, -1, &text_rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
01027 
01028   if(!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE)) {
01029     OffsetRect(&box_rect, -infoPtr->width_increment, 0);
01030     MONTHCAL_Circle(infoPtr, hdc, &box_rect);
01031   }
01032 
01033   SelectObject(hdc, old_font);
01034 }
01035 
01036 /* today mark + focus */
01037 static void MONTHCAL_PaintFocusAndCircle(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
01038 {
01039   /* circle today date if only it's in fully visible month */
01040   if (!(infoPtr->dwStyle & MCS_NOTODAYCIRCLE))
01041   {
01042     INT i;
01043 
01044     for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
01045       if (!MONTHCAL_CompareMonths(&infoPtr->todaysDate, &infoPtr->calendars[i].month))
01046       {
01047         MONTHCAL_CircleDay(infoPtr, hdc, &infoPtr->todaysDate);
01048         break;
01049       }
01050   }
01051 
01052   if (!MONTHCAL_IsDateEqual(&infoPtr->focusedSel, &st_null))
01053   {
01054     RECT r;
01055     MONTHCAL_GetDayRect(infoPtr, &infoPtr->focusedSel, &r, -1);
01056     DrawFocusRect(hdc, &r);
01057   }
01058 }
01059 
01060 /* months before first calendar month and after last calendar month */
01061 static void MONTHCAL_PaintLeadTrailMonths(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
01062 {
01063   INT mask, length, index;
01064   SYSTEMTIME st_max, st;
01065 
01066   if (infoPtr->dwStyle & MCS_NOTRAILINGDATES) return;
01067 
01068   SetTextColor(hdc, infoPtr->colors[MCSC_TRAILINGTEXT]);
01069 
01070   /* draw prev month */
01071   MONTHCAL_GetMinDate(infoPtr, &st);
01072   mask = 1 << (st.wDay-1);
01073   /* December and January both 31 days long, so no worries if wrapped */
01074   length = MONTHCAL_MonthLength(infoPtr->calendars[0].month.wMonth - 1,
01075                                 infoPtr->calendars[0].month.wYear);
01076   index = 0;
01077   while(st.wDay <= length)
01078   {
01079       MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps);
01080       mask <<= 1;
01081       st.wDay++;
01082   }
01083 
01084   /* draw next month */
01085   st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
01086   st.wDay = 1;
01087   MONTHCAL_GetNextMonth(&st);
01088   MONTHCAL_GetMaxDate(infoPtr, &st_max);
01089   mask = 1;
01090   index = MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)-1;
01091   while(st.wDay <= st_max.wDay)
01092   {
01093       MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[index] & mask, ps);
01094       mask <<= 1;
01095       st.wDay++;
01096   }
01097 }
01098 
01099 /* paint a calendar area */
01100 static void MONTHCAL_PaintCalendar(const MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps, INT calIdx)
01101 {
01102   const SYSTEMTIME *date = &infoPtr->calendars[calIdx].month;
01103   INT i, j, length;
01104   RECT r, fill_bk_rect;
01105   SYSTEMTIME st;
01106   WCHAR buf[80];
01107   HPEN old_pen;
01108   int mask;
01109 
01110   /* fill whole days area - from week days area to today note rectangle */
01111   fill_bk_rect = infoPtr->calendars[calIdx].wdays;
01112   fill_bk_rect.bottom = infoPtr->calendars[calIdx].days.bottom +
01113                           (infoPtr->todayrect.bottom - infoPtr->todayrect.top);
01114 
01115   FillRect(hdc, &fill_bk_rect, infoPtr->brushes[BrushMonth]);
01116 
01117   /* draw line under day abbreviations */
01118   old_pen = SelectObject(hdc, infoPtr->pens[PenText]);
01119   MoveToEx(hdc, infoPtr->calendars[calIdx].days.left + 3,
01120                 infoPtr->calendars[calIdx].title.bottom + infoPtr->textHeight + 1, NULL);
01121   LineTo(hdc, infoPtr->calendars[calIdx].days.right - 3,
01122               infoPtr->calendars[calIdx].title.bottom + infoPtr->textHeight + 1);
01123   SelectObject(hdc, old_pen);
01124 
01125   infoPtr->calendars[calIdx].wdays.left = infoPtr->calendars[calIdx].days.left =
01126       infoPtr->calendars[calIdx].weeknums.right;
01127 
01128   /* draw day abbreviations */
01129   SelectObject(hdc, infoPtr->hFont);
01130   SetBkColor(hdc, infoPtr->colors[MCSC_MONTHBK]);
01131   SetTextColor(hdc, infoPtr->colors[MCSC_TITLEBK]);
01132   /* rectangle to draw a single day abbreviation within */
01133   r = infoPtr->calendars[calIdx].wdays;
01134   r.right = r.left + infoPtr->width_increment;
01135 
01136   i = infoPtr->firstDay;
01137   for(j = 0; j < 7; j++) {
01138     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1 + (i+j+6)%7, buf, countof(buf));
01139     DrawTextW(hdc, buf, strlenW(buf), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
01140     OffsetRect(&r, infoPtr->width_increment, 0);
01141   }
01142 
01143   /* draw current month */
01144   SetTextColor(hdc, infoPtr->colors[MCSC_TEXT]);
01145   st = *date;
01146   st.wDay = 1;
01147   mask = 1;
01148   length = MONTHCAL_MonthLength(date->wMonth, date->wYear);
01149   while(st.wDay <= length)
01150   {
01151     MONTHCAL_DrawDay(infoPtr, hdc, &st, infoPtr->monthdayState[calIdx+1] & mask, ps);
01152     mask <<= 1;
01153     st.wDay++;
01154   }
01155 }
01156 
01157 static void MONTHCAL_Refresh(MONTHCAL_INFO *infoPtr, HDC hdc, const PAINTSTRUCT *ps)
01158 {
01159   COLORREF old_text_clr, old_bk_clr;
01160   HFONT old_font;
01161   INT i;
01162 
01163   old_text_clr = SetTextColor(hdc, comctl32_color.clrWindowText);
01164   old_bk_clr   = GetBkColor(hdc);
01165   old_font     = GetCurrentObject(hdc, OBJ_FONT);
01166 
01167   for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
01168   {
01169     RECT *title = &infoPtr->calendars[i].title;
01170     RECT r;
01171 
01172     /* draw title, redraw all its elements */
01173     if (IntersectRect(&r, &(ps->rcPaint), title))
01174         MONTHCAL_PaintTitle(infoPtr, hdc, ps, i);
01175 
01176     /* draw calendar area */
01177     UnionRect(&r, &infoPtr->calendars[i].wdays, &infoPtr->todayrect);
01178     if (IntersectRect(&r, &(ps->rcPaint), &r))
01179         MONTHCAL_PaintCalendar(infoPtr, hdc, ps, i);
01180 
01181     /* week numbers */
01182     MONTHCAL_PaintWeeknumbers(infoPtr, hdc, ps, i);
01183   }
01184 
01185   /* partially visible months */
01186   MONTHCAL_PaintLeadTrailMonths(infoPtr, hdc, ps);
01187 
01188   /* focus and today rectangle */
01189   MONTHCAL_PaintFocusAndCircle(infoPtr, hdc, ps);
01190 
01191   /* today at the bottom left */
01192   MONTHCAL_PaintTodayTitle(infoPtr, hdc, ps);
01193 
01194   /* navigation buttons */
01195   MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_BACKWARD);
01196   MONTHCAL_PaintButton(infoPtr, hdc, DIRECTION_FORWARD);
01197 
01198   /* restore context */
01199   SetBkColor(hdc, old_bk_clr);
01200   SelectObject(hdc, old_font);
01201   SetTextColor(hdc, old_text_clr);
01202 }
01203 
01204 static LRESULT
01205 MONTHCAL_GetMinReqRect(const MONTHCAL_INFO *infoPtr, RECT *rect)
01206 {
01207   TRACE("rect %p\n", rect);
01208 
01209   if(!rect) return FALSE;
01210 
01211   *rect = infoPtr->calendars[0].title;
01212   rect->bottom = infoPtr->calendars[0].days.bottom + infoPtr->todayrect.bottom -
01213                  infoPtr->todayrect.top;
01214 
01215   AdjustWindowRect(rect, infoPtr->dwStyle, FALSE);
01216 
01217   /* minimal rectangle is zero based */
01218   OffsetRect(rect, -rect->left, -rect->top);
01219 
01220   TRACE("%s\n", wine_dbgstr_rect(rect));
01221 
01222   return TRUE;
01223 }
01224 
01225 static COLORREF
01226 MONTHCAL_GetColor(const MONTHCAL_INFO *infoPtr, UINT index)
01227 {
01228   TRACE("%p, %d\n", infoPtr, index);
01229 
01230   if (index > MCSC_TRAILINGTEXT) return -1;
01231   return infoPtr->colors[index];
01232 }
01233 
01234 static LRESULT
01235 MONTHCAL_SetColor(MONTHCAL_INFO *infoPtr, UINT index, COLORREF color)
01236 {
01237   enum CachedBrush type;
01238   COLORREF prev;
01239 
01240   TRACE("%p, %d: color %08x\n", infoPtr, index, color);
01241 
01242   if (index > MCSC_TRAILINGTEXT) return -1;
01243 
01244   prev = infoPtr->colors[index];
01245   infoPtr->colors[index] = color;
01246 
01247   /* update cached brush */
01248   switch (index)
01249   {
01250   case MCSC_BACKGROUND:
01251     type = BrushBackground;
01252     break;
01253   case MCSC_TITLEBK:
01254     type = BrushTitle;
01255     break;
01256   case MCSC_MONTHBK:
01257     type = BrushMonth;
01258     break;
01259   default:
01260     type = BrushLast;
01261   }
01262 
01263   if (type != BrushLast)
01264   {
01265     DeleteObject(infoPtr->brushes[type]);
01266     infoPtr->brushes[type] = CreateSolidBrush(color);
01267   }
01268 
01269   /* update cached pen */
01270   if (index == MCSC_TEXT)
01271   {
01272     DeleteObject(infoPtr->pens[PenText]);
01273     infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[index]);
01274   }
01275 
01276   InvalidateRect(infoPtr->hwndSelf, NULL, index == MCSC_BACKGROUND ? TRUE : FALSE);
01277   return prev;
01278 }
01279 
01280 static LRESULT
01281 MONTHCAL_GetMonthDelta(const MONTHCAL_INFO *infoPtr)
01282 {
01283   TRACE("\n");
01284 
01285   if(infoPtr->delta)
01286     return infoPtr->delta;
01287   else
01288     return infoPtr->visible;
01289 }
01290 
01291 
01292 static LRESULT
01293 MONTHCAL_SetMonthDelta(MONTHCAL_INFO *infoPtr, INT delta)
01294 {
01295   INT prev = infoPtr->delta;
01296 
01297   TRACE("delta %d\n", delta);
01298 
01299   infoPtr->delta = delta;
01300   return prev;
01301 }
01302 
01303 
01304 static inline LRESULT
01305 MONTHCAL_GetFirstDayOfWeek(const MONTHCAL_INFO *infoPtr)
01306 {
01307   int day;
01308 
01309   /* convert from SYSTEMTIME to locale format */
01310   day = (infoPtr->firstDay >= 0) ? (infoPtr->firstDay+6)%7 : infoPtr->firstDay;
01311 
01312   return MAKELONG(day, infoPtr->firstDaySet);
01313 }
01314 
01315 
01316 /* Sets the first day of the week that will appear in the control
01317  *
01318  *
01319  * PARAMETERS:
01320  *  [I] infoPtr : valid pointer to control data
01321  *  [I] day : day number to set as new first day (0 == Monday,...,6 == Sunday)
01322  *
01323  *
01324  * RETURN VALUE:
01325  *  Low word contains previous first day,
01326  *  high word indicates was first day forced with this message before or is
01327  *  locale defined (TRUE - was forced, FALSE - wasn't).
01328  *
01329  * FIXME: this needs to be implemented properly in MONTHCAL_Refresh()
01330  * FIXME: we need more error checking here
01331  */
01332 static LRESULT
01333 MONTHCAL_SetFirstDayOfWeek(MONTHCAL_INFO *infoPtr, INT day)
01334 {
01335   LRESULT prev = MONTHCAL_GetFirstDayOfWeek(infoPtr);
01336   int new_day;
01337 
01338   TRACE("%d\n", day);
01339 
01340   if(day == -1)
01341   {
01342     WCHAR buf[80];
01343 
01344     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, buf, countof(buf));
01345     TRACE("%s %d\n", debugstr_w(buf), strlenW(buf));
01346 
01347     new_day = atoiW(buf);
01348 
01349     infoPtr->firstDaySet = FALSE;
01350   }
01351   else if(day >= 7)
01352   {
01353     new_day = 6; /* max first day allowed */
01354     infoPtr->firstDaySet = TRUE;
01355   }
01356   else
01357   {
01358     /* Native behaviour for that case is broken: invalid date number >31
01359        got displayed at (0,0) position, current month starts always from
01360        (1,0) position. Should be implemented here as well only if there's
01361        nothing else to do. */
01362     if (day < -1)
01363       FIXME("No bug compatibility for day=%d\n", day);
01364 
01365     new_day = day;
01366     infoPtr->firstDaySet = TRUE;
01367   }
01368 
01369   /* convert from locale to SYSTEMTIME format */
01370   infoPtr->firstDay = (new_day >= 0) ? (++new_day) % 7 : new_day;
01371 
01372   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
01373 
01374   return prev;
01375 }
01376 
01377 static LRESULT
01378 MONTHCAL_GetMaxTodayWidth(const MONTHCAL_INFO *infoPtr)
01379 {
01380   return(infoPtr->todayrect.right - infoPtr->todayrect.left);
01381 }
01382 
01383 static LRESULT
01384 MONTHCAL_SetRange(MONTHCAL_INFO *infoPtr, SHORT limits, SYSTEMTIME *range)
01385 {
01386     FILETIME ft_min, ft_max;
01387 
01388     TRACE("%x %p\n", limits, range);
01389 
01390     if ((limits & GDTR_MIN && !MONTHCAL_ValidateDate(&range[0])) ||
01391         (limits & GDTR_MAX && !MONTHCAL_ValidateDate(&range[1])))
01392         return FALSE;
01393 
01394     if (limits & GDTR_MIN)
01395     {
01396         if (!MONTHCAL_ValidateTime(&range[0]))
01397             MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);
01398 
01399         infoPtr->minDate = range[0];
01400         infoPtr->rangeValid |= GDTR_MIN;
01401     }
01402     if (limits & GDTR_MAX)
01403     {
01404         if (!MONTHCAL_ValidateTime(&range[1]))
01405             MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);
01406 
01407         infoPtr->maxDate = range[1];
01408         infoPtr->rangeValid |= GDTR_MAX;
01409     }
01410 
01411     /* Only one limit set - we are done */
01412     if ((infoPtr->rangeValid & (GDTR_MIN | GDTR_MAX)) != (GDTR_MIN | GDTR_MAX))
01413         return TRUE;
01414 
01415     SystemTimeToFileTime(&infoPtr->maxDate, &ft_max);
01416     SystemTimeToFileTime(&infoPtr->minDate, &ft_min);
01417 
01418     if (CompareFileTime(&ft_min, &ft_max) >= 0)
01419     {
01420         if ((limits & (GDTR_MIN | GDTR_MAX)) == (GDTR_MIN | GDTR_MAX))
01421         {
01422             /* Native swaps limits only when both limits are being set. */
01423             SYSTEMTIME st_tmp = infoPtr->minDate;
01424             infoPtr->minDate  = infoPtr->maxDate;
01425             infoPtr->maxDate  = st_tmp;
01426         }
01427         else
01428         {
01429             /* reset the other limit */
01430             if (limits & GDTR_MIN) infoPtr->maxDate = st_null;
01431             if (limits & GDTR_MAX) infoPtr->minDate = st_null;
01432             infoPtr->rangeValid &= limits & GDTR_MIN ? ~GDTR_MAX : ~GDTR_MIN;
01433         }
01434     }
01435 
01436     return TRUE;
01437 }
01438 
01439 
01440 static LRESULT
01441 MONTHCAL_GetRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
01442 {
01443   TRACE("%p\n", range);
01444 
01445   if(!range) return FALSE;
01446 
01447   range[1] = infoPtr->maxDate;
01448   range[0] = infoPtr->minDate;
01449 
01450   return infoPtr->rangeValid;
01451 }
01452 
01453 
01454 static LRESULT
01455 MONTHCAL_SetDayState(const MONTHCAL_INFO *infoPtr, INT months, MONTHDAYSTATE *states)
01456 {
01457   TRACE("%p %d %p\n", infoPtr, months, states);
01458 
01459   if (!(infoPtr->dwStyle & MCS_DAYSTATE)) return 0;
01460   if (months != MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)) return 0;
01461 
01462   memcpy(infoPtr->monthdayState, states, months*sizeof(MONTHDAYSTATE));
01463 
01464   return 1;
01465 }
01466 
01467 static LRESULT
01468 MONTHCAL_GetCurSel(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
01469 {
01470   TRACE("%p\n", curSel);
01471   if(!curSel) return FALSE;
01472   if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
01473 
01474   *curSel = infoPtr->minSel;
01475   TRACE("%d/%d/%d\n", curSel->wYear, curSel->wMonth, curSel->wDay);
01476   return TRUE;
01477 }
01478 
01479 static LRESULT
01480 MONTHCAL_SetCurSel(MONTHCAL_INFO *infoPtr, SYSTEMTIME *curSel)
01481 {
01482   SYSTEMTIME prev = infoPtr->minSel, selection;
01483   INT diff;
01484   WORD day;
01485 
01486   TRACE("%p\n", curSel);
01487   if(!curSel) return FALSE;
01488   if(infoPtr->dwStyle & MCS_MULTISELECT) return FALSE;
01489 
01490   if(!MONTHCAL_ValidateDate(curSel)) return FALSE;
01491   /* exit earlier if selection equals current */
01492   if (MONTHCAL_IsDateEqual(&infoPtr->minSel, curSel)) return TRUE;
01493 
01494   selection = *curSel;
01495   selection.wHour = selection.wMinute = selection.wSecond = selection.wMilliseconds = 0;
01496   MONTHCAL_CalculateDayOfWeek(&selection, TRUE);
01497 
01498   if(!MONTHCAL_IsDateInValidRange(infoPtr, &selection, FALSE)) return FALSE;
01499 
01500   /* scroll calendars only if we have to */
01501   diff = MONTHCAL_MonthDiff(&infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month, curSel);
01502   if (diff <= 0)
01503   {
01504     diff = MONTHCAL_MonthDiff(&infoPtr->calendars[0].month, curSel);
01505     if (diff > 0) diff = 0;
01506   }
01507 
01508   if (diff != 0)
01509   {
01510     INT i;
01511 
01512     for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
01513       MONTHCAL_GetMonth(&infoPtr->calendars[i].month, diff);
01514   }
01515 
01516   /* we need to store time part as it is */
01517   selection = *curSel;
01518   MONTHCAL_CalculateDayOfWeek(&selection, TRUE);
01519   infoPtr->minSel = infoPtr->maxSel = selection;
01520 
01521   /* if selection is still in current month, reduce rectangle */
01522   day = prev.wDay;
01523   prev.wDay = curSel->wDay;
01524   if (MONTHCAL_IsDateEqual(&prev, curSel))
01525   {
01526     RECT r_prev, r_new;
01527 
01528     prev.wDay = day;
01529     MONTHCAL_GetDayRect(infoPtr, &prev, &r_prev, -1);
01530     MONTHCAL_GetDayRect(infoPtr, curSel, &r_new, -1);
01531 
01532     InvalidateRect(infoPtr->hwndSelf, &r_prev, FALSE);
01533     InvalidateRect(infoPtr->hwndSelf, &r_new,  FALSE);
01534   }
01535   else
01536     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
01537 
01538   return TRUE;
01539 }
01540 
01541 
01542 static LRESULT
01543 MONTHCAL_GetMaxSelCount(const MONTHCAL_INFO *infoPtr)
01544 {
01545   return infoPtr->maxSelCount;
01546 }
01547 
01548 
01549 static LRESULT
01550 MONTHCAL_SetMaxSelCount(MONTHCAL_INFO *infoPtr, INT max)
01551 {
01552   TRACE("%d\n", max);
01553 
01554   if(!(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
01555   if(max <= 0) return FALSE;
01556 
01557   infoPtr->maxSelCount = max;
01558 
01559   return TRUE;
01560 }
01561 
01562 
01563 static LRESULT
01564 MONTHCAL_GetSelRange(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
01565 {
01566   TRACE("%p\n", range);
01567 
01568   if(!range) return FALSE;
01569 
01570   if(infoPtr->dwStyle & MCS_MULTISELECT)
01571   {
01572     range[1] = infoPtr->maxSel;
01573     range[0] = infoPtr->minSel;
01574     TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
01575     return TRUE;
01576   }
01577 
01578   return FALSE;
01579 }
01580 
01581 
01582 static LRESULT
01583 MONTHCAL_SetSelRange(MONTHCAL_INFO *infoPtr, SYSTEMTIME *range)
01584 {
01585   SYSTEMTIME old_range[2];
01586   INT diff;
01587 
01588   TRACE("%p\n", range);
01589 
01590   if(!range || !(infoPtr->dwStyle & MCS_MULTISELECT)) return FALSE;
01591 
01592   /* adjust timestamps */
01593   if(!MONTHCAL_ValidateTime(&range[0])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[0]);
01594   if(!MONTHCAL_ValidateTime(&range[1])) MONTHCAL_CopyTime(&infoPtr->todaysDate, &range[1]);
01595 
01596   /* maximum range exceeded */
01597   if(!MONTHCAL_IsSelRangeValid(infoPtr, &range[0], &range[1], NULL)) return FALSE;
01598 
01599   old_range[0] = infoPtr->minSel;
01600   old_range[1] = infoPtr->maxSel;
01601 
01602   /* swap if min > max */
01603   if(MONTHCAL_CompareSystemTime(&range[0], &range[1]) <= 0)
01604   {
01605     infoPtr->minSel = range[0];
01606     infoPtr->maxSel = range[1];
01607   }
01608   else
01609   {
01610     infoPtr->minSel = range[1];
01611     infoPtr->maxSel = range[0];
01612   }
01613 
01614   diff = MONTHCAL_MonthDiff(&infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month, &infoPtr->maxSel);
01615   if (diff < 0)
01616   {
01617     diff = MONTHCAL_MonthDiff(&infoPtr->calendars[0].month, &infoPtr->maxSel);
01618     if (diff > 0) diff = 0;
01619   }
01620 
01621   if (diff != 0)
01622   {
01623     INT i;
01624 
01625     for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
01626       MONTHCAL_GetMonth(&infoPtr->calendars[i].month, diff);
01627   }
01628 
01629   /* update day of week */
01630   MONTHCAL_CalculateDayOfWeek(&infoPtr->minSel, TRUE);
01631   MONTHCAL_CalculateDayOfWeek(&infoPtr->maxSel, TRUE);
01632 
01633   /* redraw if bounds changed */
01634   /* FIXME: no actual need to redraw everything */
01635   if(!MONTHCAL_IsDateEqual(&old_range[0], &range[0]) ||
01636      !MONTHCAL_IsDateEqual(&old_range[1], &range[1]))
01637   {
01638      InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
01639   }
01640 
01641   TRACE("[min,max]=[%d %d]\n", infoPtr->minSel.wDay, infoPtr->maxSel.wDay);
01642   return TRUE;
01643 }
01644 
01645 
01646 static LRESULT
01647 MONTHCAL_GetToday(const MONTHCAL_INFO *infoPtr, SYSTEMTIME *today)
01648 {
01649   TRACE("%p\n", today);
01650 
01651   if(!today) return FALSE;
01652   *today = infoPtr->todaysDate;
01653   return TRUE;
01654 }
01655 
01656 /* Internal helper for MCM_SETTODAY handler and auto update timer handler
01657  *
01658  * RETURN VALUE
01659  *
01660  *  TRUE  - today date changed
01661  *  FALSE - today date isn't changed
01662  */
01663 static BOOL
01664 MONTHCAL_UpdateToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today)
01665 {
01666   RECT new_r, old_r;
01667 
01668   if(MONTHCAL_IsDateEqual(today, &infoPtr->todaysDate)) return FALSE;
01669 
01670   MONTHCAL_GetDayRect(infoPtr, &infoPtr->todaysDate, &old_r, -1);
01671   MONTHCAL_GetDayRect(infoPtr, today, &new_r, -1);
01672 
01673   infoPtr->todaysDate = *today;
01674 
01675   /* only two days need redrawing */
01676   InvalidateRect(infoPtr->hwndSelf, &old_r, FALSE);
01677   InvalidateRect(infoPtr->hwndSelf, &new_r, FALSE);
01678   /* and today label */
01679   InvalidateRect(infoPtr->hwndSelf, &infoPtr->todayrect, FALSE);
01680   return TRUE;
01681 }
01682 
01683 /* MCM_SETTODAT handler */
01684 static LRESULT
01685 MONTHCAL_SetToday(MONTHCAL_INFO *infoPtr, const SYSTEMTIME *today)
01686 {
01687   TRACE("%p\n", today);
01688 
01689   if (today)
01690   {
01691     /* remember if date was set successfully */
01692     if (MONTHCAL_UpdateToday(infoPtr, today)) infoPtr->todaySet = TRUE;
01693   }
01694 
01695   return 0;
01696 }
01697 
01698 /* returns calendar index containing specified point, or -1 if it's background */
01699 static INT MONTHCAL_GetCalendarFromPoint(const MONTHCAL_INFO *infoPtr, const POINT *pt)
01700 {
01701   RECT r;
01702   INT i;
01703 
01704   for (i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
01705   {
01706      /* whole bounding rectangle allows some optimization to compute */
01707      r.left   = infoPtr->calendars[i].title.left;
01708      r.top    = infoPtr->calendars[i].title.top;
01709      r.bottom = infoPtr->calendars[i].days.bottom;
01710      r.right  = infoPtr->calendars[i].days.right;
01711 
01712      if (PtInRect(&r, *pt)) return i;
01713   }
01714 
01715   return -1;
01716 }
01717 
01718 static inline UINT fill_hittest_info(const MCHITTESTINFO *src, MCHITTESTINFO *dest)
01719 {
01720   dest->uHit = src->uHit;
01721   dest->st = src->st;
01722 
01723   if (dest->cbSize == sizeof(MCHITTESTINFO))
01724     memcpy(&dest->rc, &src->rc, sizeof(MCHITTESTINFO) - MCHITTESTINFO_V1_SIZE);
01725 
01726   return src->uHit;
01727 }
01728 
01729 static LRESULT
01730 MONTHCAL_HitTest(const MONTHCAL_INFO *infoPtr, MCHITTESTINFO *lpht)
01731 {
01732   MCHITTESTINFO htinfo;
01733   SYSTEMTIME *ht_month;
01734   INT day, calIdx;
01735 
01736   if(!lpht || lpht->cbSize < MCHITTESTINFO_V1_SIZE) return -1;
01737 
01738   htinfo.st = st_null;
01739 
01740   /* we should preserve passed fields if hit area doesn't need them */
01741   if (lpht->cbSize == sizeof(MCHITTESTINFO))
01742     memcpy(&htinfo.rc, &lpht->rc, sizeof(MCHITTESTINFO) - MCHITTESTINFO_V1_SIZE);
01743 
01744   /* Comment in for debugging...
01745   TRACE("%d %d wd[%d %d %d %d] d[%d %d %d %d] t[%d %d %d %d] wn[%d %d %d %d]\n", x, y,
01746     infoPtr->wdays.left, infoPtr->wdays.right,
01747     infoPtr->wdays.top, infoPtr->wdays.bottom,
01748     infoPtr->days.left, infoPtr->days.right,
01749     infoPtr->days.top, infoPtr->days.bottom,
01750     infoPtr->todayrect.left, infoPtr->todayrect.right,
01751     infoPtr->todayrect.top, infoPtr->todayrect.bottom,
01752     infoPtr->weeknums.left, infoPtr->weeknums.right,
01753     infoPtr->weeknums.top, infoPtr->weeknums.bottom);
01754   */
01755 
01756   /* guess in what calendar we are */
01757   calIdx = MONTHCAL_GetCalendarFromPoint(infoPtr, &lpht->pt);
01758   if (calIdx == -1)
01759   {
01760     if (PtInRect(&infoPtr->todayrect, lpht->pt))
01761     {
01762       htinfo.uHit = MCHT_TODAYLINK;
01763       htinfo.rc = infoPtr->todayrect;
01764     }
01765     else
01766       /* outside of calendar area? What's left must be background :-) */
01767       htinfo.uHit = MCHT_CALENDARBK;
01768 
01769     return fill_hittest_info(&htinfo, lpht);
01770   }
01771 
01772   /* are we in the header? */
01773   if (PtInRect(&infoPtr->calendars[calIdx].title, lpht->pt)) {
01774     /* FIXME: buttons hittesting could be optimized cause maximum
01775               two calendars have buttons */
01776     if (calIdx == 0 && PtInRect(&infoPtr->titlebtnprev, lpht->pt))
01777     {
01778       htinfo.uHit = MCHT_TITLEBTNPREV;
01779       htinfo.rc = infoPtr->titlebtnprev;
01780     }
01781     else if (PtInRect(&infoPtr->titlebtnnext, lpht->pt))
01782     {
01783       htinfo.uHit = MCHT_TITLEBTNNEXT;
01784       htinfo.rc = infoPtr->titlebtnnext;
01785     }
01786     else if (PtInRect(&infoPtr->calendars[calIdx].titlemonth, lpht->pt))
01787     {
01788       htinfo.uHit = MCHT_TITLEMONTH;
01789       htinfo.rc = infoPtr->calendars[calIdx].titlemonth;
01790       htinfo.iOffset = calIdx;
01791     }
01792     else if (PtInRect(&infoPtr->calendars[calIdx].titleyear, lpht->pt))
01793     {
01794       htinfo.uHit = MCHT_TITLEYEAR;
01795       htinfo.rc = infoPtr->calendars[calIdx].titleyear;
01796       htinfo.iOffset = calIdx;
01797     }
01798     else
01799     {
01800       htinfo.uHit = MCHT_TITLE;
01801       htinfo.rc = infoPtr->calendars[calIdx].title;
01802       htinfo.iOffset = calIdx;
01803     }
01804 
01805     return fill_hittest_info(&htinfo, lpht);
01806   }
01807 
01808   ht_month = &infoPtr->calendars[calIdx].month;
01809   /* days area (including week days and week numbers) */
01810   day = MONTHCAL_GetDayFromPos(infoPtr, lpht->pt, calIdx);
01811   if (PtInRect(&infoPtr->calendars[calIdx].wdays, lpht->pt))
01812   {
01813     htinfo.uHit = MCHT_CALENDARDAY;
01814     htinfo.iOffset = calIdx;
01815     htinfo.st.wYear  = ht_month->wYear;
01816     htinfo.st.wMonth = (day < 1) ? ht_month->wMonth -1 : ht_month->wMonth;
01817     htinfo.st.wDay   = (day < 1) ?
01818       MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day : day;
01819 
01820     MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx);
01821   }
01822   else if(PtInRect(&infoPtr->calendars[calIdx].weeknums, lpht->pt))
01823   {
01824     htinfo.uHit = MCHT_CALENDARWEEKNUM;
01825     htinfo.st.wYear = ht_month->wYear;
01826     htinfo.iOffset = calIdx;
01827 
01828     if (day < 1)
01829     {
01830       htinfo.st.wMonth = ht_month->wMonth - 1;
01831       htinfo.st.wDay = MONTHCAL_MonthLength(ht_month->wMonth-1, ht_month->wYear) - day;
01832     }
01833     else if (day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear))
01834     {
01835       htinfo.st.wMonth = ht_month->wMonth + 1;
01836       htinfo.st.wDay = day - MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear);
01837     }
01838     else
01839     {
01840       htinfo.st.wMonth = ht_month->wMonth;
01841       htinfo.st.wDay = day;
01842     }
01843   }
01844   else if(PtInRect(&infoPtr->calendars[calIdx].days, lpht->pt))
01845   {
01846       htinfo.iOffset = calIdx;
01847       htinfo.st.wYear  = ht_month->wYear;
01848       htinfo.st.wMonth = ht_month->wMonth;
01849       /* previous month only valid for first calendar */
01850       if (day < 1 && calIdx == 0)
01851       {
01852       htinfo.uHit = MCHT_CALENDARDATEPREV;
01853       MONTHCAL_GetPrevMonth(&htinfo.st);
01854       htinfo.st.wDay = MONTHCAL_MonthLength(htinfo.st.wMonth, htinfo.st.wYear) + day;
01855       }
01856       /* next month only valid for last calendar */
01857       else if (day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear) &&
01858                calIdx == MONTHCAL_GetCalCount(infoPtr)-1)
01859       {
01860       htinfo.uHit = MCHT_CALENDARDATENEXT;
01861       MONTHCAL_GetNextMonth(&htinfo.st);
01862       htinfo.st.wDay = day - MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear);
01863       }
01864       /* multiple calendars case - blank areas for previous/next month */
01865       else if (day < 1 || day > MONTHCAL_MonthLength(ht_month->wMonth, ht_month->wYear))
01866       {
01867           htinfo.uHit = MCHT_CALENDARBK;
01868       }
01869       else
01870       {
01871     htinfo.uHit = MCHT_CALENDARDATE;
01872     htinfo.st.wDay = day;
01873       }
01874 
01875       MONTHCAL_GetDayPos(infoPtr, &htinfo.st, &htinfo.iCol, &htinfo.iRow, calIdx);
01876       MONTHCAL_GetDayRectI(infoPtr, &htinfo.rc, htinfo.iCol, htinfo.iRow, calIdx);
01877       /* always update day of week */
01878       MONTHCAL_CalculateDayOfWeek(&htinfo.st, TRUE);
01879   }
01880 
01881   return fill_hittest_info(&htinfo, lpht);
01882 }
01883 
01884 /* MCN_GETDAYSTATE notification helper */
01885 static void MONTHCAL_NotifyDayState(MONTHCAL_INFO *infoPtr)
01886 {
01887   MONTHDAYSTATE *state;
01888   NMDAYSTATE nmds;
01889 
01890   if (!(infoPtr->dwStyle & MCS_DAYSTATE)) return;
01891 
01892   nmds.nmhdr.hwndFrom = infoPtr->hwndSelf;
01893   nmds.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
01894   nmds.nmhdr.code     = MCN_GETDAYSTATE;
01895   nmds.cDayState      = MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0);
01896   nmds.prgDayState    = state = Alloc(nmds.cDayState * sizeof(MONTHDAYSTATE));
01897 
01898   MONTHCAL_GetMinDate(infoPtr, &nmds.stStart);
01899   nmds.stStart.wDay = 1;
01900 
01901   SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmds.nmhdr.idFrom, (LPARAM)&nmds);
01902   memcpy(infoPtr->monthdayState, nmds.prgDayState,
01903       MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE));
01904 
01905   Free(state);
01906 }
01907 
01908 /* no valid range check performed */
01909 static void MONTHCAL_Scroll(MONTHCAL_INFO *infoPtr, INT delta)
01910 {
01911   INT i, selIdx = -1;
01912 
01913   for(i = 0; i < MONTHCAL_GetCalCount(infoPtr); i++)
01914   {
01915     /* save selection position to shift it later */
01916     if (selIdx == -1 && MONTHCAL_CompareMonths(&infoPtr->minSel, &infoPtr->calendars[i].month) == 0)
01917       selIdx = i;
01918 
01919     MONTHCAL_GetMonth(&infoPtr->calendars[i].month, delta);
01920   }
01921 
01922   /* selection is always shifted to first calendar */
01923   if(infoPtr->dwStyle & MCS_MULTISELECT)
01924   {
01925     SYSTEMTIME range[2];
01926 
01927     MONTHCAL_GetSelRange(infoPtr, range);
01928     MONTHCAL_GetMonth(&range[0], delta - selIdx);
01929     MONTHCAL_GetMonth(&range[1], delta - selIdx);
01930     MONTHCAL_SetSelRange(infoPtr, range);
01931   }
01932   else
01933   {
01934     SYSTEMTIME st = infoPtr->minSel;
01935 
01936     MONTHCAL_GetMonth(&st, delta - selIdx);
01937     MONTHCAL_SetCurSel(infoPtr, &st);
01938   }
01939 }
01940 
01941 static void MONTHCAL_GoToMonth(MONTHCAL_INFO *infoPtr, enum nav_direction direction)
01942 {
01943   INT delta = infoPtr->delta ? infoPtr->delta : MONTHCAL_GetCalCount(infoPtr);
01944   SYSTEMTIME st;
01945 
01946   TRACE("%s\n", direction == DIRECTION_BACKWARD ? "back" : "fwd");
01947 
01948   /* check if change allowed by range set */
01949   if(direction == DIRECTION_BACKWARD)
01950   {
01951     st = infoPtr->calendars[0].month;
01952     MONTHCAL_GetMonth(&st, -delta);
01953   }
01954   else
01955   {
01956     st = infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
01957     MONTHCAL_GetMonth(&st, delta);
01958   }
01959 
01960   if(!MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE)) return;
01961 
01962   MONTHCAL_Scroll(infoPtr, direction == DIRECTION_BACKWARD ? -delta : delta);
01963   MONTHCAL_NotifyDayState(infoPtr);
01964   MONTHCAL_NotifySelectionChange(infoPtr);
01965 }
01966 
01967 static LRESULT
01968 MONTHCAL_RButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
01969 {
01970   static const WCHAR todayW[] = { 'G','o',' ','t','o',' ','T','o','d','a','y',':',0 };
01971   HMENU hMenu;
01972   POINT menupoint;
01973   WCHAR buf[32];
01974 
01975   hMenu = CreatePopupMenu();
01976   if (!LoadStringW(COMCTL32_hModule, IDM_GOTODAY, buf, countof(buf)))
01977   {
01978       WARN("Can't load resource\n");
01979       strcpyW(buf, todayW);
01980   }
01981   AppendMenuW(hMenu, MF_STRING|MF_ENABLED, 1, buf);
01982   menupoint.x = (short)LOWORD(lParam);
01983   menupoint.y = (short)HIWORD(lParam);
01984   ClientToScreen(infoPtr->hwndSelf, &menupoint);
01985   if( TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD,
01986              menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL))
01987   {
01988       if (infoPtr->dwStyle & MCS_MULTISELECT)
01989       {
01990           SYSTEMTIME range[2];
01991 
01992           range[0] = range[1] = infoPtr->todaysDate;
01993           MONTHCAL_SetSelRange(infoPtr, range);
01994       }
01995       else
01996           MONTHCAL_SetCurSel(infoPtr, &infoPtr->todaysDate);
01997 
01998       MONTHCAL_NotifySelectionChange(infoPtr);
01999       MONTHCAL_NotifySelect(infoPtr);
02000   }
02001 
02002   return 0;
02003 }
02004 
02005 /***
02006  * DESCRIPTION:
02007  * Subclassed edit control windproc function
02008  *
02009  * PARAMETER(S):
02010  * [I] hwnd : the edit window handle
02011  * [I] uMsg : the message that is to be processed
02012  * [I] wParam : first message parameter
02013  * [I] lParam : second message parameter
02014  *
02015  */
02016 static LRESULT CALLBACK EditWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
02017 {
02018     MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(GetParent(hwnd), 0);
02019 
02020     TRACE("(hwnd=%p, uMsg=%x, wParam=%lx, lParam=%lx)\n",
02021       hwnd, uMsg, wParam, lParam);
02022 
02023     switch (uMsg)
02024     {
02025     case WM_GETDLGCODE:
02026       return DLGC_WANTARROWS | DLGC_WANTALLKEYS;
02027 
02028     case WM_DESTROY:
02029     {
02030         WNDPROC editProc = infoPtr->EditWndProc;
02031         infoPtr->EditWndProc = NULL;
02032         SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (DWORD_PTR)editProc);
02033         return CallWindowProcW(editProc, hwnd, uMsg, wParam, lParam);
02034     }
02035 
02036     case WM_KILLFOCUS:
02037         break;
02038 
02039     case WM_KEYDOWN:
02040         if ((VK_ESCAPE == (INT)wParam) || (VK_RETURN == (INT)wParam))
02041         break;
02042 
02043     default:
02044         return CallWindowProcW(infoPtr->EditWndProc, hwnd, uMsg, wParam, lParam);
02045     }
02046 
02047     SendMessageW(infoPtr->hWndYearUpDown, WM_CLOSE, 0, 0);
02048     SendMessageW(hwnd, WM_CLOSE, 0, 0);
02049     return 0;
02050 }
02051 
02052 /* creates updown control and edit box */
02053 static void MONTHCAL_EditYear(MONTHCAL_INFO *infoPtr, INT calIdx)
02054 {
02055     RECT *rc = &infoPtr->calendars[calIdx].titleyear;
02056     RECT *title = &infoPtr->calendars[calIdx].title;
02057 
02058     infoPtr->hWndYearEdit =
02059     CreateWindowExW(0, WC_EDITW, 0, WS_VISIBLE | WS_CHILD | ES_READONLY,
02060             rc->left + 3, (title->bottom + title->top - infoPtr->textHeight) / 2,
02061             rc->right - rc->left + 4,
02062             infoPtr->textHeight, infoPtr->hwndSelf,
02063             NULL, NULL, NULL);
02064 
02065     SendMessageW(infoPtr->hWndYearEdit, WM_SETFONT, (WPARAM)infoPtr->hBoldFont, TRUE);
02066 
02067     infoPtr->hWndYearUpDown =
02068     CreateWindowExW(0, UPDOWN_CLASSW, 0,
02069             WS_VISIBLE | WS_CHILD | UDS_SETBUDDYINT | UDS_NOTHOUSANDS | UDS_ARROWKEYS,
02070             rc->right + 7, (title->bottom + title->top - infoPtr->textHeight) / 2,
02071             18, infoPtr->textHeight, infoPtr->hwndSelf,
02072             NULL, NULL, NULL);
02073 
02074     /* attach edit box */
02075     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETRANGE, 0,
02076                  MAKELONG(max_allowed_date.wYear, min_allowed_date.wYear));
02077     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETBUDDY, (WPARAM)infoPtr->hWndYearEdit, 0);
02078     SendMessageW(infoPtr->hWndYearUpDown, UDM_SETPOS, 0, infoPtr->calendars[calIdx].month.wYear);
02079 
02080     /* subclass edit box */
02081     infoPtr->EditWndProc = (WNDPROC)SetWindowLongPtrW(infoPtr->hWndYearEdit,
02082                                   GWLP_WNDPROC, (DWORD_PTR)EditWndProc);
02083 
02084     SetFocus(infoPtr->hWndYearEdit);
02085 }
02086 
02087 static LRESULT
02088 MONTHCAL_LButtonDown(MONTHCAL_INFO *infoPtr, LPARAM lParam)
02089 {
02090   MCHITTESTINFO ht;
02091   DWORD hit;
02092 
02093   /* Actually we don't need input focus for calendar, this is used to kill
02094      year updown and its buddy edit box */
02095   if (IsWindow(infoPtr->hWndYearUpDown))
02096   {
02097       SetFocus(infoPtr->hwndSelf);
02098       return 0;
02099   }
02100 
02101   SetCapture(infoPtr->hwndSelf);
02102 
02103   ht.cbSize = sizeof(MCHITTESTINFO);
02104   ht.pt.x = (short)LOWORD(lParam);
02105   ht.pt.y = (short)HIWORD(lParam);
02106 
02107   hit = MONTHCAL_HitTest(infoPtr, &ht);
02108 
02109   TRACE("%x at (%d, %d)\n", hit, ht.pt.x, ht.pt.y);
02110 
02111   switch(hit)
02112   {
02113   case MCHT_TITLEBTNNEXT:
02114     MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD);
02115     infoPtr->status = MC_NEXTPRESSED;
02116     SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
02117     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
02118     return 0;
02119 
02120   case MCHT_TITLEBTNPREV:
02121     MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD);
02122     infoPtr->status = MC_PREVPRESSED;
02123     SetTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER, MC_PREVNEXTMONTHDELAY, 0);
02124     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
02125     return 0;
02126 
02127   case MCHT_TITLEMONTH:
02128   {
02129     HMENU hMenu = CreatePopupMenu();
02130     WCHAR buf[32];
02131     POINT menupoint;
02132     INT i;
02133 
02134     for (i = 0; i < 12; i++)
02135     {
02136     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+i, buf, countof(buf));
02137     AppendMenuW(hMenu, MF_STRING|MF_ENABLED, i + 1, buf);
02138     }
02139     menupoint.x = ht.pt.x;
02140     menupoint.y = ht.pt.y;
02141     ClientToScreen(infoPtr->hwndSelf, &menupoint);
02142     i = TrackPopupMenu(hMenu,TPM_LEFTALIGN | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_RETURNCMD,
02143                menupoint.x, menupoint.y, 0, infoPtr->hwndSelf, NULL);
02144 
02145     if ((i > 0) && (i < 13) && infoPtr->calendars[ht.iOffset].month.wMonth != i)
02146     {
02147         INT delta = i - infoPtr->calendars[ht.iOffset].month.wMonth;
02148         SYSTEMTIME st;
02149 
02150         /* check if change allowed by range set */
02151         st = delta < 0 ? infoPtr->calendars[0].month :
02152                          infoPtr->calendars[MONTHCAL_GetCalCount(infoPtr)-1].month;
02153         MONTHCAL_GetMonth(&st, delta);
02154 
02155         if (MONTHCAL_IsDateInValidRange(infoPtr, &st, FALSE))
02156         {
02157             MONTHCAL_Scroll(infoPtr, delta);
02158             MONTHCAL_NotifyDayState(infoPtr);
02159             MONTHCAL_NotifySelectionChange(infoPtr);
02160             InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
02161         }
02162     }
02163     return 0;
02164   }
02165   case MCHT_TITLEYEAR:
02166   {
02167     MONTHCAL_EditYear(infoPtr, ht.iOffset);
02168     return 0;
02169   }
02170   case MCHT_TODAYLINK:
02171   {
02172     if (infoPtr->dwStyle & MCS_MULTISELECT)
02173     {
02174         SYSTEMTIME range[2];
02175 
02176         range[0] = range[1] = infoPtr->todaysDate;
02177         MONTHCAL_SetSelRange(infoPtr, range);
02178     }
02179     else
02180         MONTHCAL_SetCurSel(infoPtr, &infoPtr->todaysDate);
02181 
02182     MONTHCAL_NotifySelectionChange(infoPtr);
02183     MONTHCAL_NotifySelect(infoPtr);
02184     return 0;
02185   }
02186   case MCHT_CALENDARDATENEXT:
02187   case MCHT_CALENDARDATEPREV:
02188   case MCHT_CALENDARDATE:
02189   {
02190     SYSTEMTIME st[2];
02191 
02192     MONTHCAL_CopyDate(&ht.st, &infoPtr->firstSel);
02193 
02194     st[0] = st[1] = ht.st;
02195     /* clear selection range */
02196     MONTHCAL_SetSelRange(infoPtr, st);
02197 
02198     infoPtr->status = MC_SEL_LBUTDOWN;
02199     MONTHCAL_SetDayFocus(infoPtr, &ht.st);
02200     return 0;
02201   }
02202   }
02203 
02204   return 1;
02205 }
02206 
02207 
02208 static LRESULT
02209 MONTHCAL_LButtonUp(MONTHCAL_INFO *infoPtr, LPARAM lParam)
02210 {
02211   NMHDR nmhdr;
02212   MCHITTESTINFO ht;
02213   DWORD hit;
02214 
02215   TRACE("\n");
02216 
02217   if(infoPtr->status & (MC_PREVPRESSED | MC_NEXTPRESSED)) {
02218     RECT *r;
02219 
02220     KillTimer(infoPtr->hwndSelf, MC_PREVNEXTMONTHTIMER);
02221     r = infoPtr->status & MC_PREVPRESSED ? &infoPtr->titlebtnprev : &infoPtr->titlebtnnext;
02222     infoPtr->status &= ~(MC_PREVPRESSED | MC_NEXTPRESSED);
02223 
02224     InvalidateRect(infoPtr->hwndSelf, r, FALSE);
02225   }
02226 
02227   ReleaseCapture();
02228 
02229   /* always send NM_RELEASEDCAPTURE notification */
02230   nmhdr.hwndFrom = infoPtr->hwndSelf;
02231   nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
02232   nmhdr.code     = NM_RELEASEDCAPTURE;
02233   TRACE("Sent notification from %p to %p\n", infoPtr->hwndSelf, infoPtr->hwndNotify);
02234 
02235   SendMessageW(infoPtr->hwndNotify, WM_NOTIFY, nmhdr.idFrom, (LPARAM)&nmhdr);
02236 
02237   if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
02238 
02239   ht.cbSize = sizeof(MCHITTESTINFO);
02240   ht.pt.x = (short)LOWORD(lParam);
02241   ht.pt.y = (short)HIWORD(lParam);
02242   hit = MONTHCAL_HitTest(infoPtr, &ht);
02243 
02244   infoPtr->status = MC_SEL_LBUTUP;
02245   MONTHCAL_SetDayFocus(infoPtr, NULL);
02246 
02247   if((hit & MCHT_CALENDARDATE) == MCHT_CALENDARDATE)
02248   {
02249     SYSTEMTIME sel = infoPtr->minSel;
02250 
02251     /* will be invalidated here */
02252     MONTHCAL_SetCurSel(infoPtr, &ht.st);
02253 
02254     /* send MCN_SELCHANGE only if new date selected */
02255     if (!MONTHCAL_IsDateEqual(&sel, &ht.st))
02256         MONTHCAL_NotifySelectionChange(infoPtr);
02257 
02258     MONTHCAL_NotifySelect(infoPtr);
02259   }
02260 
02261   return 0;
02262 }
02263 
02264 
02265 static LRESULT
02266 MONTHCAL_Timer(MONTHCAL_INFO *infoPtr, WPARAM id)
02267 {
02268   TRACE("%ld\n", id);
02269 
02270   switch(id) {
02271   case MC_PREVNEXTMONTHTIMER:
02272     if(infoPtr->status & MC_NEXTPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_FORWARD);
02273     if(infoPtr->status & MC_PREVPRESSED) MONTHCAL_GoToMonth(infoPtr, DIRECTION_BACKWARD);
02274     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
02275     break;
02276   case MC_TODAYUPDATETIMER:
02277   {
02278     SYSTEMTIME st;
02279 
02280     if(infoPtr->todaySet) return 0;
02281 
02282     GetLocalTime(&st);
02283     MONTHCAL_UpdateToday(infoPtr, &st);
02284 
02285     /* notification sent anyway */
02286     MONTHCAL_NotifySelectionChange(infoPtr);
02287 
02288     return 0;
02289   }
02290   default:
02291     ERR("got unknown timer %ld\n", id);
02292     break;
02293   }
02294 
02295   return 0;
02296 }
02297 
02298 
02299 static LRESULT
02300 MONTHCAL_MouseMove(MONTHCAL_INFO *infoPtr, LPARAM lParam)
02301 {
02302   MCHITTESTINFO ht;
02303   SYSTEMTIME st_ht;
02304   INT hit;
02305   RECT r;
02306 
02307   if(!(infoPtr->status & MC_SEL_LBUTDOWN)) return 0;
02308 
02309   ht.cbSize = sizeof(MCHITTESTINFO);
02310   ht.pt.x = (short)LOWORD(lParam);
02311   ht.pt.y = (short)HIWORD(lParam);
02312   ht.iOffset = -1;
02313 
02314   hit = MONTHCAL_HitTest(infoPtr, &ht);
02315 
02316   /* not on the calendar date numbers? bail out */
02317   TRACE("hit:%x\n",hit);
02318   if((hit & MCHT_CALENDARDATE) != MCHT_CALENDARDATE)
02319   {
02320     MONTHCAL_SetDayFocus(infoPtr, NULL);
02321     return 0;
02322   }
02323 
02324   st_ht = ht.st;
02325 
02326   /* if pointer is over focused day still there's nothing to do */
02327   if(!MONTHCAL_SetDayFocus(infoPtr, &ht.st)) return 0;
02328 
02329   MONTHCAL_GetDayRect(infoPtr, &ht.st, &r, ht.iOffset);
02330 
02331   if(infoPtr->dwStyle & MCS_MULTISELECT) {
02332     SYSTEMTIME st[2];
02333 
02334     MONTHCAL_GetSelRange(infoPtr, st);
02335 
02336     /* If we're still at the first selected date and range is empty, return.
02337        If range isn't empty we should change range to a single firstSel */
02338     if(MONTHCAL_IsDateEqual(&infoPtr->firstSel, &st_ht) &&
02339        MONTHCAL_IsDateEqual(&st[0], &st[1])) goto done;
02340 
02341     MONTHCAL_IsSelRangeValid(infoPtr, &st_ht, &infoPtr->firstSel, &st_ht);
02342 
02343     st[0] = infoPtr->firstSel;
02344     /* we should overwrite timestamp here */
02345     MONTHCAL_CopyDate(&st_ht, &st[1]);
02346 
02347     /* bounds will be swapped here if needed */
02348     MONTHCAL_SetSelRange(infoPtr, st);
02349 
02350     return 0;
02351   }
02352 
02353 done:
02354 
02355   /* FIXME: this should specify a rectangle containing only the days that changed
02356      using InvalidateRect */
02357   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
02358 
02359   return 0;
02360 }
02361 
02362 
02363 static LRESULT
02364 MONTHCAL_Paint(MONTHCAL_INFO *infoPtr, HDC hdc_paint)
02365 {
02366   HDC hdc;
02367   PAINTSTRUCT ps;
02368 
02369   if (hdc_paint)
02370   {
02371     GetClientRect(infoPtr->hwndSelf, &ps.rcPaint);
02372     hdc = hdc_paint;
02373   }
02374   else
02375     hdc = BeginPaint(infoPtr->hwndSelf, &ps);
02376 
02377   MONTHCAL_Refresh(infoPtr, hdc, &ps);
02378   if (!hdc_paint) EndPaint(infoPtr->hwndSelf, &ps);
02379   return 0;
02380 }
02381 
02382 static LRESULT
02383 MONTHCAL_EraseBkgnd(const MONTHCAL_INFO *infoPtr, HDC hdc)
02384 {
02385   RECT rc;
02386 
02387   if (!GetClipBox(hdc, &rc)) return FALSE;
02388 
02389   FillRect(hdc, &rc, infoPtr->brushes[BrushBackground]);
02390 
02391   return TRUE;
02392 }
02393 
02394 static LRESULT
02395 MONTHCAL_PrintClient(MONTHCAL_INFO *infoPtr, HDC hdc, DWORD options)
02396 {
02397   FIXME("Partial Stub: (hdc=%p options=0x%08x)\n", hdc, options);
02398 
02399   if ((options & PRF_CHECKVISIBLE) && !IsWindowVisible(infoPtr->hwndSelf))
02400       return 0;
02401 
02402   if (options & PRF_ERASEBKGND)
02403       MONTHCAL_EraseBkgnd(infoPtr, hdc);
02404 
02405   if (options & PRF_CLIENT)
02406       MONTHCAL_Paint(infoPtr, hdc);
02407 
02408   return 0;
02409 }
02410 
02411 static LRESULT
02412 MONTHCAL_SetFocus(const MONTHCAL_INFO *infoPtr)
02413 {
02414   TRACE("\n");
02415 
02416   InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
02417 
02418   return 0;
02419 }
02420 
02421 /* sets the size information */
02422 static void MONTHCAL_UpdateSize(MONTHCAL_INFO *infoPtr)
02423 {
02424   static const WCHAR O0W[] = { '0','0',0 };
02425   RECT *title=&infoPtr->calendars[0].title;
02426   RECT *prev=&infoPtr->titlebtnprev;
02427   RECT *next=&infoPtr->titlebtnnext;
02428   RECT *titlemonth=&infoPtr->calendars[0].titlemonth;
02429   RECT *titleyear=&infoPtr->calendars[0].titleyear;
02430   RECT *wdays=&infoPtr->calendars[0].wdays;
02431   RECT *weeknumrect=&infoPtr->calendars[0].weeknums;
02432   RECT *days=&infoPtr->calendars[0].days;
02433   RECT *todayrect=&infoPtr->todayrect;
02434 
02435   INT xdiv, dx, dy, i, j, x, y, c_dx, c_dy;
02436   WCHAR buff[80];
02437   TEXTMETRICW tm;
02438   SIZE size, sz;
02439   RECT client;
02440   HFONT font;
02441   HDC hdc;
02442 
02443   GetClientRect(infoPtr->hwndSelf, &client);
02444 
02445   hdc = GetDC(infoPtr->hwndSelf);
02446   font = SelectObject(hdc, infoPtr->hFont);
02447 
02448   /* get the height and width of each day's text */
02449   GetTextMetricsW(hdc, &tm);
02450   infoPtr->textHeight = tm.tmHeight + tm.tmExternalLeading + tm.tmInternalLeading;
02451 
02452   /* find largest abbreviated day name for current locale */
02453   size.cx = sz.cx = 0;
02454   for (i = 0; i < 7; i++)
02455   {
02456       if(GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1 + i,
02457                         buff, countof(buff)))
02458       {
02459           GetTextExtentPoint32W(hdc, buff, lstrlenW(buff), &sz);
02460           if (sz.cx > size.cx) size.cx = sz.cx;
02461       }
02462       else /* locale independent fallback on failure */
02463       {
02464           static const WCHAR SunW[] = { 'S','u','n',0 };
02465 
02466           GetTextExtentPoint32W(hdc, SunW, lstrlenW(SunW), &size);
02467           break;
02468       }
02469   }
02470 
02471   infoPtr->textWidth = size.cx + 2;
02472 
02473   /* recalculate the height and width increments and offsets */
02474   GetTextExtentPoint32W(hdc, O0W, 2, &size);
02475 
02476   /* restore the originally selected font */
02477   SelectObject(hdc, font);
02478   ReleaseDC(infoPtr->hwndSelf, hdc);
02479 
02480   xdiv = (infoPtr->dwStyle & MCS_WEEKNUMBERS) ? 8 : 7;
02481 
02482   infoPtr->width_increment  = size.cx * 2 + 4;
02483   infoPtr->height_increment = infoPtr->textHeight;
02484 
02485   /* calculate title area */
02486   title->top    = 0;
02487   title->bottom = 3 * infoPtr->height_increment / 2;
02488   title->left   = 0;
02489   title->right  = infoPtr->width_increment * xdiv;
02490 
02491   /* set the dimensions of the next and previous buttons and center */
02492   /* the month text vertically */
02493   prev->top    = next->top    = title->top + 4;
02494   prev->bottom = next->bottom = title->bottom - 4;
02495   prev->left   = title->left + 4;
02496   prev->right  = prev->left + (title->bottom - title->top);
02497   next->right  = title->right - 4;
02498   next->left   = next->right - (title->bottom - title->top);
02499 
02500   /* titlemonth->left and right change based upon the current month
02501      and are recalculated in refresh as the current month may change
02502      without the control being resized */
02503   titlemonth->top    = titleyear->top    = title->top    + (infoPtr->height_increment)/2;
02504   titlemonth->bottom = titleyear->bottom = title->bottom - (infoPtr->height_increment)/2;
02505 
02506   /* week numbers */
02507   weeknumrect->left  = 0;
02508   weeknumrect->right = infoPtr->dwStyle & MCS_WEEKNUMBERS ? prev->right : 0;
02509 
02510   /* days abbreviated names */
02511   wdays->left   = days->left   = weeknumrect->right;
02512   wdays->right  = days->right  = wdays->left + 7 * infoPtr->width_increment;
02513   wdays->top    = title->bottom;
02514   wdays->bottom = wdays->top + infoPtr->height_increment;
02515 
02516   days->top    = weeknumrect->top = wdays->bottom;
02517   days->bottom = weeknumrect->bottom = days->top + 6 * infoPtr->height_increment;
02518 
02519   todayrect->left   = 0;
02520   todayrect->right  = title->right;
02521   todayrect->top    = days->bottom;
02522   todayrect->bottom = days->bottom + infoPtr->height_increment;
02523 
02524   /* compute calendar count, update all calendars */
02525   x = (client.right  + MC_CALENDAR_PADDING) / (title->right - title->left + MC_CALENDAR_PADDING);
02526   /* today label affects whole height */
02527   if (infoPtr->dwStyle & MCS_NOTODAY)
02528     y = (client.bottom + MC_CALENDAR_PADDING) / (days->bottom - title->top + MC_CALENDAR_PADDING);
02529   else
02530     y = (client.bottom - todayrect->bottom + todayrect->top + MC_CALENDAR_PADDING) /
02531          (days->bottom - title->top + MC_CALENDAR_PADDING);
02532 
02533   /* TODO: ensure that count is properly adjusted to fit 12 months constraint */
02534   if (x == 0) x = 1;
02535   if (y == 0) y = 1;
02536 
02537   if (x*y != MONTHCAL_GetCalCount(infoPtr))
02538   {
02539       infoPtr->dim.cx = x;
02540       infoPtr->dim.cy = y;
02541       infoPtr->calendars = ReAlloc(infoPtr->calendars, MONTHCAL_GetCalCount(infoPtr)*sizeof(CALENDAR_INFO));
02542 
02543       infoPtr->monthdayState = ReAlloc(infoPtr->monthdayState,
02544           MONTHCAL_GetMonthRange(infoPtr, GMR_DAYSTATE, 0)*sizeof(MONTHDAYSTATE));
02545       MONTHCAL_NotifyDayState(infoPtr);
02546 
02547       /* update pointers that we'll need */
02548       title = &infoPtr->calendars[0].title;
02549       wdays = &infoPtr->calendars[0].wdays;
02550       days  = &infoPtr->calendars[0].days;
02551   }
02552 
02553   for (i = 1; i < MONTHCAL_GetCalCount(infoPtr); i++)
02554   {
02555       /* set months */
02556       infoPtr->calendars[i] = infoPtr->calendars[0];
02557       MONTHCAL_GetMonth(&infoPtr->calendars[i].month, i);
02558   }
02559 
02560   /* offset all rectangles to center in client area */
02561   c_dx = (client.right  - x * title->right - MC_CALENDAR_PADDING * (x-1)) / 2;
02562   c_dy = (client.bottom - y * todayrect->bottom - MC_CALENDAR_PADDING * (y-1)) / 2;
02563 
02564   /* if calendar doesn't fit client area show it at left/top bounds */
02565   if (title->left + c_dx < 0) c_dx = 0;
02566   if (title->top  + c_dy < 0) c_dy = 0;
02567 
02568   for (i = 0; i < y; i++)
02569   {
02570       for (j = 0; j < x; j++)
02571       {
02572           dx = j*(title->right - title->left + MC_CALENDAR_PADDING) + c_dx;
02573           dy = i*(days->bottom - title->top  + MC_CALENDAR_PADDING) + c_dy;
02574 
02575           OffsetRect(&infoPtr->calendars[i*x+j].title, dx, dy);
02576           OffsetRect(&infoPtr->calendars[i*x+j].titlemonth, dx, dy);
02577           OffsetRect(&infoPtr->calendars[i*x+j].titleyear, dx, dy);
02578           OffsetRect(&infoPtr->calendars[i*x+j].wdays, dx, dy);
02579           OffsetRect(&infoPtr->calendars[i*x+j].weeknums, dx, dy);
02580           OffsetRect(&infoPtr->calendars[i*x+j].days, dx, dy);
02581       }
02582   }
02583 
02584   OffsetRect(prev, c_dx, c_dy);
02585   OffsetRect(next, (x-1)*(title->right - title->left + MC_CALENDAR_PADDING) + c_dx, c_dy);
02586 
02587   i = infoPtr->dim.cx * infoPtr->dim.cy - infoPtr->dim.cx;
02588   todayrect->left   = infoPtr->calendars[i].title.left;
02589   todayrect->right  = infoPtr->calendars[i].title.right;
02590   todayrect->top    = infoPtr->calendars[i].days.bottom;
02591   todayrect->bottom = infoPtr->calendars[i].days.bottom + infoPtr->height_increment;
02592 
02593   TRACE("dx=%d dy=%d client[%s] title[%s] wdays[%s] days[%s] today[%s]\n",
02594     infoPtr->width_increment,infoPtr->height_increment,
02595         wine_dbgstr_rect(&client),
02596         wine_dbgstr_rect(title),
02597         wine_dbgstr_rect(wdays),
02598         wine_dbgstr_rect(days),
02599         wine_dbgstr_rect(todayrect));
02600 }
02601 
02602 static LRESULT MONTHCAL_Size(MONTHCAL_INFO *infoPtr, int Width, int Height)
02603 {
02604   TRACE("(width=%d, height=%d)\n", Width, Height);
02605 
02606   MONTHCAL_UpdateSize(infoPtr);
02607   InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
02608 
02609   return 0;
02610 }
02611 
02612 static LRESULT MONTHCAL_GetFont(const MONTHCAL_INFO *infoPtr)
02613 {
02614     return (LRESULT)infoPtr->hFont;
02615 }
02616 
02617 static LRESULT MONTHCAL_SetFont(MONTHCAL_INFO *infoPtr, HFONT hFont, BOOL redraw)
02618 {
02619     HFONT hOldFont;
02620     LOGFONTW lf;
02621 
02622     if (!hFont) return 0;
02623 
02624     hOldFont = infoPtr->hFont;
02625     infoPtr->hFont = hFont;
02626 
02627     GetObjectW(infoPtr->hFont, sizeof(lf), &lf);
02628     lf.lfWeight = FW_BOLD;
02629     infoPtr->hBoldFont = CreateFontIndirectW(&lf);
02630 
02631     MONTHCAL_UpdateSize(infoPtr);
02632 
02633     if (redraw)
02634         InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
02635 
02636     return (LRESULT)hOldFont;
02637 }
02638 
02639 /* update theme after a WM_THEMECHANGED message */
02640 static LRESULT theme_changed (const MONTHCAL_INFO* infoPtr)
02641 {
02642     HTHEME theme = GetWindowTheme (infoPtr->hwndSelf);
02643     CloseThemeData (theme);
02644     OpenThemeData (infoPtr->hwndSelf, themeClass);
02645     return 0;
02646 }
02647 
02648 static INT MONTHCAL_StyleChanged(MONTHCAL_INFO *infoPtr, WPARAM wStyleType,
02649                                  const STYLESTRUCT *lpss)
02650 {
02651     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
02652           wStyleType, lpss->styleOld, lpss->styleNew);
02653 
02654     if (wStyleType != GWL_STYLE) return 0;
02655 
02656     infoPtr->dwStyle = lpss->styleNew;
02657 
02658     /* make room for week numbers */
02659     if ((lpss->styleNew ^ lpss->styleOld) & MCS_WEEKNUMBERS)
02660         MONTHCAL_UpdateSize(infoPtr);
02661 
02662     return 0;
02663 }
02664 
02665 static INT MONTHCAL_StyleChanging(MONTHCAL_INFO *infoPtr, WPARAM wStyleType,
02666                                   STYLESTRUCT *lpss)
02667 {
02668     TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
02669           wStyleType, lpss->styleOld, lpss->styleNew);
02670 
02671     /* block MCS_MULTISELECT change */
02672     if ((lpss->styleNew ^ lpss->styleOld) & MCS_MULTISELECT)
02673     {
02674         if (lpss->styleOld & MCS_MULTISELECT)
02675             lpss->styleNew |= MCS_MULTISELECT;
02676         else
02677             lpss->styleNew &= ~MCS_MULTISELECT;
02678     }
02679 
02680     /* block MCS_DAYSTATE change */
02681     if ((lpss->styleNew ^ lpss->styleOld) & MCS_DAYSTATE)
02682     {
02683         if (lpss->styleOld & MCS_DAYSTATE)
02684             lpss->styleNew |= MCS_DAYSTATE;
02685         else
02686             lpss->styleNew &= ~MCS_DAYSTATE;
02687     }
02688 
02689     return 0;
02690 }
02691 
02692 /* FIXME: check whether dateMin/dateMax need to be adjusted. */
02693 static LRESULT
02694 MONTHCAL_Create(HWND hwnd, LPCREATESTRUCTW lpcs)
02695 {
02696   MONTHCAL_INFO *infoPtr;
02697 
02698   /* allocate memory for info structure */
02699   infoPtr = Alloc(sizeof(MONTHCAL_INFO));
02700   SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
02701 
02702   if (infoPtr == NULL) {
02703     ERR("could not allocate info memory!\n");
02704     return 0;
02705   }
02706 
02707   infoPtr->hwndSelf = hwnd;
02708   infoPtr->hwndNotify = lpcs->hwndParent;
02709   infoPtr->dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
02710   infoPtr->dim.cx = infoPtr->dim.cy = 1;
02711   infoPtr->calendars = Alloc(sizeof(CALENDAR_INFO));
02712   if (!infoPtr->calendars) goto fail;
02713   infoPtr->monthdayState = Alloc(3*sizeof(MONTHDAYSTATE));
02714   if (!infoPtr->monthdayState) goto fail;
02715 
02716   /* initialize info structure */
02717   /* FIXME: calculate systemtime ->> localtime(subtract timezoneinfo) */
02718 
02719   GetLocalTime(&infoPtr->todaysDate);
02720   MONTHCAL_SetFirstDayOfWeek(infoPtr, -1);
02721 
02722   infoPtr->maxSelCount   = (infoPtr->dwStyle & MCS_MULTISELECT) ? 7 : 1;
02723 
02724   infoPtr->colors[MCSC_BACKGROUND]   = comctl32_color.clrWindow;
02725   infoPtr->colors[MCSC_TEXT]         = comctl32_color.clrWindowText;
02726   infoPtr->colors[MCSC_TITLEBK]      = comctl32_color.clrActiveCaption;
02727   infoPtr->colors[MCSC_TITLETEXT]    = comctl32_color.clrWindow;
02728   infoPtr->colors[MCSC_MONTHBK]      = comctl32_color.clrWindow;
02729   infoPtr->colors[MCSC_TRAILINGTEXT] = comctl32_color.clrGrayText;
02730 
02731   infoPtr->brushes[BrushBackground]  = CreateSolidBrush(infoPtr->colors[MCSC_BACKGROUND]);
02732   infoPtr->brushes[BrushTitle]       = CreateSolidBrush(infoPtr->colors[MCSC_TITLEBK]);
02733   infoPtr->brushes[BrushMonth]       = CreateSolidBrush(infoPtr->colors[MCSC_MONTHBK]);
02734 
02735   infoPtr->pens[PenRed]  = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
02736   infoPtr->pens[PenText] = CreatePen(PS_SOLID, 1, infoPtr->colors[MCSC_TEXT]);
02737 
02738   infoPtr->minSel = infoPtr->todaysDate;
02739   infoPtr->maxSel = infoPtr->todaysDate;
02740   infoPtr->calendars[0].month = infoPtr->todaysDate;
02741   infoPtr->isUnicode = TRUE;
02742 
02743   /* setup control layout and day state data */
02744   MONTHCAL_UpdateSize(infoPtr);
02745 
02746   /* today auto update timer, to be freed only on control destruction */
02747   SetTimer(infoPtr->hwndSelf, MC_TODAYUPDATETIMER, MC_TODAYUPDATEDELAY, 0);
02748 
02749   OpenThemeData (infoPtr->hwndSelf, themeClass);
02750 
02751   return 0;
02752 
02753 fail:
02754   Free(infoPtr->monthdayState);
02755   Free(infoPtr->calendars);
02756   Free(infoPtr);
02757   return 0;
02758 }
02759 
02760 static LRESULT
02761 MONTHCAL_Destroy(MONTHCAL_INFO *infoPtr)
02762 {
02763   INT i;
02764 
02765   /* free month calendar info data */
02766   Free(infoPtr->monthdayState);
02767   Free(infoPtr->calendars);
02768   SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
02769 
02770   CloseThemeData (GetWindowTheme (infoPtr->hwndSelf));
02771 
02772   for (i = 0; i < BrushLast; i++) DeleteObject(infoPtr->brushes[i]);
02773   for (i = 0; i < PenLast; i++) DeleteObject(infoPtr->pens[i]);
02774 
02775   Free(infoPtr);
02776   return 0;
02777 }
02778 
02779 /*
02780  * Handler for WM_NOTIFY messages
02781  */
02782 static LRESULT
02783 MONTHCAL_Notify(MONTHCAL_INFO *infoPtr, NMHDR *hdr)
02784 {
02785   /* notification from year edit updown */
02786   if (hdr->code == UDN_DELTAPOS)
02787   {
02788     NMUPDOWN *nmud = (NMUPDOWN*)hdr;
02789 
02790     if (hdr->hwndFrom == infoPtr->hWndYearUpDown && nmud->iDelta)
02791     {
02792       /* year value limits are set up explicitly after updown creation */
02793       MONTHCAL_Scroll(infoPtr, 12 * nmud->iDelta);
02794       MONTHCAL_NotifyDayState(infoPtr);
02795       MONTHCAL_NotifySelectionChange(infoPtr);
02796     }
02797   }
02798   return 0;
02799 }
02800 
02801 static inline BOOL
02802 MONTHCAL_SetUnicodeFormat(MONTHCAL_INFO *infoPtr, BOOL isUnicode)
02803 {
02804   BOOL prev = infoPtr->isUnicode;
02805   infoPtr->isUnicode = isUnicode;
02806   return prev;
02807 }
02808 
02809 static inline BOOL
02810 MONTHCAL_GetUnicodeFormat(const MONTHCAL_INFO *infoPtr)
02811 {
02812   return infoPtr->isUnicode;
02813 }
02814 
02815 static LRESULT WINAPI
02816 MONTHCAL_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
02817 {
02818   MONTHCAL_INFO *infoPtr = (MONTHCAL_INFO *)GetWindowLongPtrW(hwnd, 0);
02819 
02820   TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
02821 
02822   if (!infoPtr && (uMsg != WM_CREATE))
02823     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
02824   switch(uMsg)
02825   {
02826   case MCM_GETCURSEL:
02827     return MONTHCAL_GetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
02828 
02829   case MCM_SETCURSEL:
02830     return MONTHCAL_SetCurSel(infoPtr, (LPSYSTEMTIME)lParam);
02831 
02832   case MCM_GETMAXSELCOUNT:
02833     return MONTHCAL_GetMaxSelCount(infoPtr);
02834 
02835   case MCM_SETMAXSELCOUNT:
02836     return MONTHCAL_SetMaxSelCount(infoPtr, wParam);
02837 
02838   case MCM_GETSELRANGE:
02839     return MONTHCAL_GetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
02840 
02841   case MCM_SETSELRANGE:
02842     return MONTHCAL_SetSelRange(infoPtr, (LPSYSTEMTIME)lParam);
02843 
02844   case MCM_GETMONTHRANGE:
02845     return MONTHCAL_GetMonthRange(infoPtr, wParam, (SYSTEMTIME*)lParam);
02846 
02847   case MCM_SETDAYSTATE:
02848     return MONTHCAL_SetDayState(infoPtr, (INT)wParam, (LPMONTHDAYSTATE)lParam);
02849 
02850   case MCM_GETMINREQRECT:
02851     return MONTHCAL_GetMinReqRect(infoPtr, (LPRECT)lParam);
02852 
02853   case MCM_GETCOLOR:
02854     return MONTHCAL_GetColor(infoPtr, wParam);
02855 
02856   case MCM_SETCOLOR:
02857     return MONTHCAL_SetColor(infoPtr, wParam, (COLORREF)lParam);
02858 
02859   case MCM_GETTODAY:
02860     return MONTHCAL_GetToday(infoPtr, (LPSYSTEMTIME)lParam);
02861 
02862   case MCM_SETTODAY:
02863     return MONTHCAL_SetToday(infoPtr, (LPSYSTEMTIME)lParam);
02864 
02865   case MCM_HITTEST:
02866     return MONTHCAL_HitTest(infoPtr, (PMCHITTESTINFO)lParam);
02867 
02868   case MCM_GETFIRSTDAYOFWEEK:
02869     return MONTHCAL_GetFirstDayOfWeek(infoPtr);
02870 
02871   case MCM_SETFIRSTDAYOFWEEK:
02872     return MONTHCAL_SetFirstDayOfWeek(infoPtr, (INT)lParam);
02873 
02874   case MCM_GETRANGE:
02875     return MONTHCAL_GetRange(infoPtr, (LPSYSTEMTIME)lParam);
02876 
02877   case MCM_SETRANGE:
02878     return MONTHCAL_SetRange(infoPtr, (SHORT)wParam, (LPSYSTEMTIME)lParam);
02879 
02880   case MCM_GETMONTHDELTA:
02881     return MONTHCAL_GetMonthDelta(infoPtr);
02882 
02883   case MCM_SETMONTHDELTA:
02884     return MONTHCAL_SetMonthDelta(infoPtr, wParam);
02885 
02886   case MCM_GETMAXTODAYWIDTH:
02887     return MONTHCAL_GetMaxTodayWidth(infoPtr);
02888 
02889   case MCM_SETUNICODEFORMAT:
02890     return MONTHCAL_SetUnicodeFormat(infoPtr, (BOOL)wParam);
02891 
02892   case MCM_GETUNICODEFORMAT:
02893     return MONTHCAL_GetUnicodeFormat(infoPtr);
02894 
02895   case MCM_GETCALENDARCOUNT:
02896     return MONTHCAL_GetCalCount(infoPtr);
02897 
02898   case WM_GETDLGCODE:
02899     return DLGC_WANTARROWS | DLGC_WANTCHARS;
02900 
02901   case WM_RBUTTONUP:
02902     return MONTHCAL_RButtonUp(infoPtr, lParam);
02903 
02904   case WM_LBUTTONDOWN:
02905     return MONTHCAL_LButtonDown(infoPtr, lParam);
02906 
02907   case WM_MOUSEMOVE:
02908     return MONTHCAL_MouseMove(infoPtr, lParam);
02909 
02910   case WM_LBUTTONUP:
02911     return MONTHCAL_LButtonUp(infoPtr, lParam);
02912 
02913   case WM_PAINT:
02914     return MONTHCAL_Paint(infoPtr, (HDC)wParam);
02915 
02916   case WM_PRINTCLIENT:
02917     return MONTHCAL_PrintClient(infoPtr, (HDC)wParam, (DWORD)lParam);
02918 
02919   case WM_ERASEBKGND:
02920     return MONTHCAL_EraseBkgnd(infoPtr, (HDC)wParam);
02921 
02922   case WM_SETFOCUS:
02923     return MONTHCAL_SetFocus(infoPtr);
02924 
02925   case WM_SIZE:
02926     return MONTHCAL_Size(infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
02927 
02928   case WM_NOTIFY:
02929     return MONTHCAL_Notify(infoPtr, (NMHDR*)lParam);
02930 
02931   case WM_CREATE:
02932     return MONTHCAL_Create(hwnd, (LPCREATESTRUCTW)lParam);
02933 
02934   case WM_SETFONT:
02935     return MONTHCAL_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
02936 
02937   case WM_GETFONT:
02938     return MONTHCAL_GetFont(infoPtr);
02939 
02940   case WM_TIMER:
02941     return MONTHCAL_Timer(infoPtr, wParam);
02942     
02943   case WM_THEMECHANGED:
02944     return theme_changed (infoPtr);
02945 
02946   case WM_DESTROY:
02947     return MONTHCAL_Destroy(infoPtr);
02948 
02949   case WM_SYSCOLORCHANGE:
02950     COMCTL32_RefreshSysColors();
02951     return 0;
02952 
02953   case WM_STYLECHANGED:
02954     return MONTHCAL_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
02955 
02956   case WM_STYLECHANGING:
02957     return MONTHCAL_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
02958 
02959   default:
02960     if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
02961       ERR( "unknown msg %04x wp=%08lx lp=%08lx\n", uMsg, wParam, lParam);
02962     return DefWindowProcW(hwnd, uMsg, wParam, lParam);
02963   }
02964 }
02965 
02966 
02967 void
02968 MONTHCAL_Register(void)
02969 {
02970   WNDCLASSW wndClass;
02971 
02972   ZeroMemory(&wndClass, sizeof(WNDCLASSW));
02973   wndClass.style         = CS_GLOBALCLASS;
02974   wndClass.lpfnWndProc   = MONTHCAL_WindowProc;
02975   wndClass.cbClsExtra    = 0;
02976   wndClass.cbWndExtra    = sizeof(MONTHCAL_INFO *);
02977   wndClass.hCursor       = LoadCursorW(0, (LPWSTR)IDC_ARROW);
02978   wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
02979   wndClass.lpszClassName = MONTHCAL_CLASSW;
02980 
02981   RegisterClassW(&wndClass);
02982 }
02983 
02984 
02985 void
02986 MONTHCAL_Unregister(void)
02987 {
02988     UnregisterClassW(MONTHCAL_CLASSW, NULL);
02989 }

Generated on Sun May 27 2012 04:21:05 for ReactOS by doxygen 1.7.6.1

ReactOS is a registered trademark or a trademark of ReactOS Foundation in the United States and other countries.