a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2445 lines
74 KiB
C#
2445 lines
74 KiB
C#
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
// Copyright (c) 2004-2006 Novell, Inc.
|
|
//
|
|
// Authors:
|
|
// John BouAntoun jba-mono@optusnet.com.au
|
|
//
|
|
// REMAINING TODO:
|
|
// - get the date_cell_size and title_size to be pixel perfect match of SWF
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.ComponentModel;
|
|
using System.ComponentModel.Design;
|
|
using System.Drawing;
|
|
using System.Globalization;
|
|
using System.Windows.Forms;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace System.Windows.Forms {
|
|
[DefaultBindingProperty("SelectionRange")]
|
|
[ClassInterface(ClassInterfaceType.AutoDispatch)]
|
|
[ComVisible(true)]
|
|
[DefaultProperty("SelectionRange")]
|
|
[DefaultEvent("DateChanged")]
|
|
[Designer ("System.Windows.Forms.Design.MonthCalendarDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
|
|
public class MonthCalendar : Control {
|
|
#region Local variables
|
|
ArrayList annually_bolded_dates;
|
|
ArrayList monthly_bolded_dates;
|
|
ArrayList bolded_dates;
|
|
Size calendar_dimensions;
|
|
Day first_day_of_week;
|
|
DateTime max_date;
|
|
int max_selection_count;
|
|
DateTime min_date;
|
|
int scroll_change;
|
|
SelectionRange selection_range;
|
|
bool show_today;
|
|
bool show_today_circle;
|
|
bool show_week_numbers;
|
|
Color title_back_color;
|
|
Color title_fore_color;
|
|
DateTime today_date;
|
|
bool today_date_set;
|
|
Color trailing_fore_color;
|
|
ContextMenu today_menu;
|
|
ContextMenu month_menu;
|
|
Timer timer;
|
|
Timer updown_timer;
|
|
const int initial_delay = 500;
|
|
const int subsequent_delay = 100;
|
|
private bool is_year_going_up;
|
|
private bool is_year_going_down;
|
|
private bool is_mouse_moving_year;
|
|
private int year_moving_count;
|
|
private bool date_selected_event_pending;
|
|
bool right_to_left_layout;
|
|
|
|
// internal variables used
|
|
internal bool show_year_updown;
|
|
internal DateTime current_month; // the month that is being displayed in top left corner of the grid
|
|
internal DateTimePicker owner; // used if this control is popped up
|
|
internal int button_x_offset;
|
|
internal Size button_size;
|
|
internal Size title_size;
|
|
internal Size date_cell_size;
|
|
internal Size calendar_spacing;
|
|
internal int divider_line_offset;
|
|
internal DateTime clicked_date;
|
|
internal Rectangle clicked_rect;
|
|
internal bool is_date_clicked;
|
|
internal bool is_previous_clicked;
|
|
internal bool is_next_clicked;
|
|
internal bool is_shift_pressed;
|
|
internal DateTime first_select_start_date;
|
|
internal int last_clicked_calendar_index;
|
|
internal Rectangle last_clicked_calendar_rect;
|
|
internal Font bold_font; // Cache the font in FontStyle.Bold
|
|
internal StringFormat centered_format; // Cache centered string format
|
|
private Point month_title_click_location;
|
|
// this is used to see which item was actually clicked on in the beginning
|
|
// so that we know which item to fire on timer
|
|
// 0: date clicked
|
|
// 1: previous clicked
|
|
// 2: next clicked
|
|
private bool[] click_state;
|
|
|
|
|
|
|
|
#endregion // Local variables
|
|
|
|
#region Public Constructors
|
|
|
|
public MonthCalendar () {
|
|
// set up the control painting
|
|
SetStyle (ControlStyles.UserPaint | ControlStyles.StandardClick, false);
|
|
|
|
// mouse down timer
|
|
timer = new Timer ();
|
|
timer.Interval = 500;
|
|
timer.Enabled = false;
|
|
|
|
// initialise default values
|
|
DateTime now = DateTime.Now.Date;
|
|
selection_range = new SelectionRange (now, now);
|
|
today_date = now;
|
|
current_month = new DateTime (now.Year , now.Month, 1);
|
|
|
|
// iniatialise local members
|
|
annually_bolded_dates = null;
|
|
bolded_dates = null;
|
|
calendar_dimensions = new Size (1,1);
|
|
first_day_of_week = Day.Default;
|
|
max_date = new DateTime (9998, 12, 31);
|
|
max_selection_count = 7;
|
|
min_date = new DateTime (1753, 1, 1);
|
|
monthly_bolded_dates = null;
|
|
scroll_change = 0;
|
|
show_today = true;
|
|
show_today_circle = true;
|
|
show_week_numbers = false;
|
|
title_back_color = ThemeEngine.Current.ColorActiveCaption;
|
|
title_fore_color = ThemeEngine.Current.ColorActiveCaptionText;
|
|
today_date_set = false;
|
|
trailing_fore_color = SystemColors.GrayText;
|
|
bold_font = new Font (Font, Font.Style | FontStyle.Bold);
|
|
centered_format = new StringFormat (StringFormat.GenericTypographic);
|
|
centered_format.FormatFlags = centered_format.FormatFlags | StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.NoWrap | StringFormatFlags.FitBlackBox;
|
|
centered_format.FormatFlags &= ~StringFormatFlags.NoClip;
|
|
centered_format.LineAlignment = StringAlignment.Center;
|
|
centered_format.Alignment = StringAlignment.Center;
|
|
|
|
// Set default values
|
|
ForeColor = SystemColors.WindowText;
|
|
BackColor = ThemeEngine.Current.ColorWindow;
|
|
|
|
// intiailise internal variables used
|
|
button_x_offset = 5;
|
|
button_size = new Size (22, 17);
|
|
// default settings based on 8.25 pt San Serif Font
|
|
// Not sure of algorithm used to establish this
|
|
date_cell_size = new Size (24, 16); // default size at san-serif 8.25
|
|
divider_line_offset = 4;
|
|
calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid
|
|
|
|
// set some state info
|
|
clicked_date = now;
|
|
is_date_clicked = false;
|
|
is_previous_clicked = false;
|
|
is_next_clicked = false;
|
|
is_shift_pressed = false;
|
|
click_state = new bool [] {false, false, false};
|
|
first_select_start_date = now;
|
|
month_title_click_location = Point.Empty;
|
|
|
|
// set up context menus
|
|
SetUpTodayMenu ();
|
|
SetUpMonthMenu ();
|
|
|
|
// event handlers
|
|
timer.Tick += new EventHandler (TimerHandler);
|
|
MouseMove += new MouseEventHandler (MouseMoveHandler);
|
|
MouseDown += new MouseEventHandler (MouseDownHandler);
|
|
KeyDown += new KeyEventHandler (KeyDownHandler);
|
|
MouseUp += new MouseEventHandler (MouseUpHandler);
|
|
KeyUp += new KeyEventHandler (KeyUpHandler);
|
|
|
|
// this replaces paint so call the control version
|
|
base.Paint += new PaintEventHandler (PaintHandler);
|
|
|
|
Size = DefaultSize;
|
|
}
|
|
|
|
// called when this control is added to date time picker
|
|
internal MonthCalendar (DateTimePicker owner) : this () {
|
|
this.owner = owner;
|
|
this.is_visible = false;
|
|
this.Size = this.DefaultSize;
|
|
}
|
|
|
|
#endregion // Public Constructors
|
|
|
|
#region Public Instance Properties
|
|
|
|
// dates to make bold on calendar annually (recurring)
|
|
[Localizable (true)]
|
|
public DateTime [] AnnuallyBoldedDates {
|
|
set {
|
|
if (annually_bolded_dates == null)
|
|
annually_bolded_dates = new ArrayList (value);
|
|
else {
|
|
annually_bolded_dates.Clear ();
|
|
annually_bolded_dates.AddRange (value);
|
|
}
|
|
|
|
UpdateBoldedDates ();
|
|
}
|
|
get {
|
|
if (annually_bolded_dates == null || annually_bolded_dates.Count == 0) {
|
|
return new DateTime [0];
|
|
}
|
|
DateTime [] result = new DateTime [annually_bolded_dates.Count];
|
|
annually_bolded_dates.CopyTo (result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
[Browsable(false)]
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public override Image BackgroundImage {
|
|
get {
|
|
return base.BackgroundImage;
|
|
}
|
|
set {
|
|
base.BackgroundImage = value;
|
|
}
|
|
}
|
|
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
[Browsable (false)]
|
|
public override ImageLayout BackgroundImageLayout {
|
|
get {
|
|
return base.BackgroundImageLayout;
|
|
}
|
|
set {
|
|
base.BackgroundImageLayout = value;
|
|
}
|
|
}
|
|
|
|
// the back color for the main part of the calendar
|
|
public override Color BackColor {
|
|
set {
|
|
base.BackColor = value;
|
|
}
|
|
get {
|
|
return base.BackColor;
|
|
}
|
|
}
|
|
|
|
// specific dates to make bold on calendar (non-recurring)
|
|
[Localizable (true)]
|
|
public DateTime[] BoldedDates {
|
|
set {
|
|
if (bolded_dates == null) {
|
|
bolded_dates = new ArrayList (value);
|
|
} else {
|
|
bolded_dates.Clear ();
|
|
bolded_dates.AddRange (value);
|
|
}
|
|
|
|
UpdateBoldedDates ();
|
|
}
|
|
get {
|
|
if (bolded_dates == null || bolded_dates.Count == 0)
|
|
return new DateTime [0];
|
|
|
|
DateTime [] result = new DateTime [bolded_dates.Count];
|
|
bolded_dates.CopyTo (result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// the configuration of the monthly grid display - only allowed to display at most,
|
|
// 1 calendar year at a time, will be trimmed to fit it properly
|
|
[Localizable (true)]
|
|
public Size CalendarDimensions {
|
|
set {
|
|
if (value.Width < 0 || value.Height < 0) {
|
|
throw new ArgumentException ();
|
|
}
|
|
if (calendar_dimensions != value) {
|
|
// squeeze the grid into 1 calendar year
|
|
if (value.Width * value.Height > 12) {
|
|
// iteratively reduce the largest dimension till our
|
|
// product is less than 12
|
|
if (value.Width > 12 && value.Height > 12) {
|
|
calendar_dimensions = new Size (4, 3);
|
|
} else if (value.Width > 12) {
|
|
for (int i = 12; i > 0; i--) {
|
|
if (i * value.Height <= 12) {
|
|
calendar_dimensions = new Size (i, value.Height);
|
|
break;
|
|
}
|
|
}
|
|
} else if (value.Height > 12) {
|
|
for (int i = 12; i > 0; i--) {
|
|
if (i * value.Width <= 12) {
|
|
calendar_dimensions = new Size (value.Width, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
calendar_dimensions = value;
|
|
}
|
|
this.Invalidate ();
|
|
}
|
|
}
|
|
get {
|
|
return calendar_dimensions;
|
|
}
|
|
}
|
|
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
protected override bool DoubleBuffered {
|
|
get {
|
|
return base.DoubleBuffered;
|
|
}
|
|
set {
|
|
base.DoubleBuffered = value;
|
|
}
|
|
}
|
|
|
|
// the first day of the week to display
|
|
[Localizable (true)]
|
|
[DefaultValue (Day.Default)]
|
|
public Day FirstDayOfWeek {
|
|
set {
|
|
if (first_day_of_week != value) {
|
|
first_day_of_week = value;
|
|
this.Invalidate ();
|
|
}
|
|
}
|
|
get {
|
|
return first_day_of_week;
|
|
}
|
|
}
|
|
|
|
// the fore color for the main part of the calendar
|
|
public override Color ForeColor {
|
|
set {
|
|
base.ForeColor = value;
|
|
}
|
|
get {
|
|
return base.ForeColor;
|
|
}
|
|
}
|
|
|
|
[Browsable(false)]
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public new ImeMode ImeMode {
|
|
get { return base.ImeMode; }
|
|
set { base.ImeMode = value; }
|
|
}
|
|
|
|
// the maximum date allowed to be selected on this month calendar
|
|
public DateTime MaxDate {
|
|
set {
|
|
if (value < MinDate) {
|
|
string msg = string.Format (CultureInfo.CurrentCulture,
|
|
"Value of '{0}' is not valid for 'MaxDate'. 'MaxDate' " +
|
|
"must be greater than or equal to MinDate.",
|
|
value.ToString ("d", CultureInfo.CurrentCulture));
|
|
throw new ArgumentOutOfRangeException ("MaxDate",
|
|
msg);
|
|
}
|
|
|
|
if (max_date == value)
|
|
return;
|
|
|
|
max_date = value;
|
|
|
|
if (max_date < selection_range.Start || max_date < selection_range.End) {
|
|
DateTime start = max_date < selection_range.Start ? max_date : selection_range.Start;
|
|
DateTime end = max_date < selection_range.End ? max_date : selection_range.End;
|
|
SelectionRange = new SelectionRange (start, end);
|
|
}
|
|
}
|
|
get {
|
|
return max_date;
|
|
}
|
|
}
|
|
|
|
// the maximum number of selectable days
|
|
[DefaultValue (7)]
|
|
public int MaxSelectionCount {
|
|
set {
|
|
if (value < 1) {
|
|
string msg = string.Format (CultureInfo.CurrentCulture,
|
|
"Value of '{0}' is not valid for 'MaxSelectionCount'. " +
|
|
"'MaxSelectionCount' must be greater than or equal to {1}.",
|
|
value, 1);
|
|
throw new ArgumentOutOfRangeException ("MaxSelectionCount",
|
|
msg);
|
|
}
|
|
|
|
// can't set selectioncount less than already selected dates
|
|
if ((SelectionEnd - SelectionStart).Days > value) {
|
|
throw new ArgumentException();
|
|
}
|
|
|
|
if (max_selection_count != value) {
|
|
max_selection_count = value;
|
|
this.OnUIAMaxSelectionCountChanged ();
|
|
}
|
|
}
|
|
get {
|
|
return max_selection_count;
|
|
}
|
|
}
|
|
|
|
// the minimum date allowed to be selected on this month calendar
|
|
public DateTime MinDate {
|
|
set {
|
|
DateTime absoluteMinDate = new DateTime (1753, 1, 1);
|
|
|
|
if (value < absoluteMinDate) {
|
|
string msg = string.Format (CultureInfo.CurrentCulture,
|
|
"Value of '{0}' is not valid for 'MinDate'. 'MinDate' " +
|
|
"must be greater than or equal to {1}.",
|
|
value.ToString ("d", CultureInfo.CurrentCulture),
|
|
absoluteMinDate.ToString ("d", CultureInfo.CurrentCulture));
|
|
throw new ArgumentOutOfRangeException ("MinDate",
|
|
msg);
|
|
}
|
|
|
|
if (value > MaxDate) {
|
|
string msg = string.Format (CultureInfo.CurrentCulture,
|
|
"Value of '{0}' is not valid for 'MinDate'. 'MinDate' " +
|
|
"must be less than MaxDate.",
|
|
value.ToString ("d", CultureInfo.CurrentCulture));
|
|
throw new ArgumentOutOfRangeException ("MinDate",
|
|
msg);
|
|
}
|
|
|
|
if (min_date == value)
|
|
return;
|
|
|
|
min_date = value;
|
|
|
|
if (min_date > selection_range.Start || min_date > selection_range.End) {
|
|
DateTime start = min_date > selection_range.Start ? min_date : selection_range.Start;
|
|
DateTime end = min_date > selection_range.End ? min_date : selection_range.End;
|
|
SelectionRange = new SelectionRange (start, end);
|
|
}
|
|
}
|
|
get {
|
|
return min_date;
|
|
}
|
|
}
|
|
|
|
// dates to make bold on calendar monthly (recurring)
|
|
[Localizable (true)]
|
|
public DateTime[] MonthlyBoldedDates {
|
|
set {
|
|
if (monthly_bolded_dates == null) {
|
|
monthly_bolded_dates = new ArrayList (value);
|
|
} else {
|
|
monthly_bolded_dates.Clear ();
|
|
monthly_bolded_dates.AddRange (value);
|
|
}
|
|
|
|
UpdateBoldedDates ();
|
|
}
|
|
get {
|
|
if (monthly_bolded_dates == null || monthly_bolded_dates.Count == 0)
|
|
return new DateTime [0];
|
|
|
|
DateTime [] result = new DateTime [monthly_bolded_dates.Count];
|
|
monthly_bolded_dates.CopyTo (result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
|
|
[Browsable (false)]
|
|
// Padding should not have any effect on the appearance of MonthCalendar.
|
|
public new Padding Padding {
|
|
get {
|
|
return base.Padding;
|
|
}
|
|
set {
|
|
base.Padding = value;
|
|
}
|
|
}
|
|
|
|
[DefaultValue (false)]
|
|
[Localizable (true)]
|
|
public virtual bool RightToLeftLayout {
|
|
get {
|
|
return right_to_left_layout;
|
|
}
|
|
set {
|
|
right_to_left_layout = value;
|
|
}
|
|
}
|
|
|
|
// the ammount by which to scroll this calendar by
|
|
[DefaultValue (0)]
|
|
public int ScrollChange {
|
|
set {
|
|
if (value < 0 || value > 20000) {
|
|
throw new ArgumentException();
|
|
}
|
|
|
|
if (scroll_change != value) {
|
|
scroll_change = value;
|
|
}
|
|
}
|
|
get {
|
|
return scroll_change;
|
|
}
|
|
}
|
|
|
|
|
|
// the last selected date
|
|
[Browsable (false)]
|
|
[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
|
|
public DateTime SelectionEnd {
|
|
set {
|
|
if (value < MinDate || value > MaxDate) {
|
|
throw new ArgumentException();
|
|
}
|
|
|
|
if (SelectionRange.End != value) {
|
|
DateTime old_end = SelectionRange.End;
|
|
// make sure the end obeys the max selection range count
|
|
if (value < SelectionRange.Start) {
|
|
SelectionRange.Start = value;
|
|
}
|
|
if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) {
|
|
SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1);
|
|
}
|
|
SelectionRange.End = value;
|
|
this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End));
|
|
this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
|
|
this.OnUIASelectionChanged ();
|
|
}
|
|
}
|
|
get {
|
|
return SelectionRange.End;
|
|
}
|
|
}
|
|
|
|
[Bindable(true)]
|
|
// the range of selected dates
|
|
public SelectionRange SelectionRange {
|
|
set {
|
|
if (selection_range != value) {
|
|
if (value.Start < MinDate)
|
|
throw new ArgumentException ("SelectionStart cannot be less than MinDate");
|
|
else if (value.End > MaxDate)
|
|
throw new ArgumentException ("SelectionEnd cannot be greated than MaxDate");
|
|
|
|
SelectionRange old_range = selection_range;
|
|
|
|
// make sure the end obeys the max selection range count
|
|
if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) {
|
|
selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End);
|
|
} else {
|
|
selection_range = value;
|
|
}
|
|
SelectionRange visible_range = this.GetDisplayRange(true);
|
|
if(visible_range.Start > selection_range.End) {
|
|
this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1);
|
|
this.Invalidate ();
|
|
} else if (visible_range.End < selection_range.Start) {
|
|
int year_diff = selection_range.End.Year - visible_range.End.Year;
|
|
int month_diff = selection_range.End.Month - visible_range.End.Month;
|
|
this.current_month = current_month.AddMonths(year_diff * 12 + month_diff);
|
|
this.Invalidate ();
|
|
}
|
|
// invalidate the selected range changes
|
|
DateTime diff_start = old_range.Start;
|
|
DateTime diff_end = old_range.End;
|
|
// now decide which region is greated
|
|
if (old_range.Start > SelectionRange.Start) {
|
|
diff_start = SelectionRange.Start;
|
|
} else if (old_range.Start == SelectionRange.Start) {
|
|
if (old_range.End < SelectionRange.End) {
|
|
diff_start = old_range.End;
|
|
} else {
|
|
diff_start = SelectionRange.End;
|
|
}
|
|
}
|
|
if (old_range.End < SelectionRange.End) {
|
|
diff_end = SelectionRange.End;
|
|
} else if (old_range.End == SelectionRange.End) {
|
|
if (old_range.Start < SelectionRange.Start) {
|
|
diff_end = SelectionRange.Start;
|
|
} else {
|
|
diff_end = old_range.Start;
|
|
}
|
|
}
|
|
|
|
|
|
// invalidate the region required
|
|
SelectionRange new_range = new SelectionRange (diff_start, diff_end);
|
|
if (new_range.End != old_range.End || new_range.Start != old_range.Start)
|
|
this.InvalidateDateRange (new_range);
|
|
// raise date changed event
|
|
this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
|
|
this.OnUIASelectionChanged ();
|
|
}
|
|
}
|
|
get {
|
|
return selection_range;
|
|
}
|
|
}
|
|
|
|
// the first selected date
|
|
[Browsable (false)]
|
|
[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
|
|
public DateTime SelectionStart {
|
|
set {
|
|
if (value < MinDate || value > MaxDate) {
|
|
throw new ArgumentException();
|
|
}
|
|
|
|
if (SelectionRange.Start != value) {
|
|
// make sure the end obeys the max selection range count
|
|
if (value > SelectionRange.End) {
|
|
SelectionRange.End = value;
|
|
} else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) {
|
|
SelectionRange.End = value.AddDays(MaxSelectionCount-1);
|
|
}
|
|
SelectionRange.Start = value;
|
|
DateTime new_month = new DateTime(value.Year, value.Month, 1);
|
|
if (current_month != new_month)
|
|
current_month = new_month;
|
|
|
|
this.Invalidate ();
|
|
this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
|
|
this.OnUIASelectionChanged ();
|
|
}
|
|
}
|
|
get {
|
|
return selection_range.Start;
|
|
}
|
|
}
|
|
|
|
// whether or not to show todays date
|
|
[DefaultValue (true)]
|
|
public bool ShowToday {
|
|
set {
|
|
if (show_today != value) {
|
|
show_today = value;
|
|
this.Invalidate ();
|
|
}
|
|
}
|
|
get {
|
|
return show_today;
|
|
}
|
|
}
|
|
|
|
// whether or not to show a circle around todays date
|
|
[DefaultValue (true)]
|
|
public bool ShowTodayCircle {
|
|
set {
|
|
if (show_today_circle != value) {
|
|
show_today_circle = value;
|
|
this.Invalidate ();
|
|
}
|
|
}
|
|
get {
|
|
return show_today_circle;
|
|
}
|
|
}
|
|
|
|
// whether or not to show numbers beside each row of weeks
|
|
[Localizable (true)]
|
|
[DefaultValue (false)]
|
|
public bool ShowWeekNumbers {
|
|
set {
|
|
if (show_week_numbers != value) {
|
|
show_week_numbers = value;
|
|
// The values here don't matter, SetBoundsCore will calculate its own
|
|
SetBoundsCore (Left, Top, Width, Height, BoundsSpecified.Width);
|
|
this.Invalidate ();
|
|
}
|
|
}
|
|
get {
|
|
return show_week_numbers;
|
|
}
|
|
}
|
|
|
|
// the rectangle size required to render one month based on current font
|
|
[Browsable (false)]
|
|
[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
|
|
public Size SingleMonthSize {
|
|
get {
|
|
if (this.Font == null) {
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
// multiplier is sucked out from the font size
|
|
int multiplier = this.Font.Height;
|
|
|
|
// establis how many columns and rows we have
|
|
int column_count = (ShowWeekNumbers) ? 8 : 7;
|
|
int row_count = 7; // not including the today date
|
|
|
|
// set the date_cell_size and the title_size
|
|
date_cell_size = new Size ((int) Math.Ceiling (1.8 * multiplier), multiplier);
|
|
title_size = new Size ((date_cell_size.Width * column_count), 2 * multiplier);
|
|
|
|
return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height);
|
|
}
|
|
}
|
|
|
|
[Localizable(false)]
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public new Size Size {
|
|
get {
|
|
return base.Size;
|
|
}
|
|
set {
|
|
base.Size = value;
|
|
}
|
|
}
|
|
|
|
[Bindable(false)]
|
|
[Browsable(false)]
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public override string Text {
|
|
get {
|
|
return base.Text;
|
|
}
|
|
set {
|
|
base.Text = value;
|
|
}
|
|
}
|
|
|
|
// the back color for the title of the calendar and the
|
|
// forecolor for the day of the week text
|
|
public Color TitleBackColor {
|
|
set {
|
|
if (title_back_color != value) {
|
|
title_back_color = value;
|
|
this.Invalidate ();
|
|
}
|
|
}
|
|
get {
|
|
return title_back_color;
|
|
}
|
|
}
|
|
|
|
// the fore color for the title of the calendar
|
|
public Color TitleForeColor {
|
|
set {
|
|
if (title_fore_color != value) {
|
|
title_fore_color = value;
|
|
this.Invalidate ();
|
|
}
|
|
}
|
|
get {
|
|
return title_fore_color;
|
|
}
|
|
}
|
|
|
|
// the date this calendar is using to refer to today's date
|
|
public DateTime TodayDate {
|
|
set {
|
|
today_date_set = true;
|
|
if (today_date != value) {
|
|
today_date = value;
|
|
this.Invalidate ();
|
|
}
|
|
}
|
|
get {
|
|
return today_date;
|
|
}
|
|
}
|
|
|
|
// tells if user specifically set today_date for this control
|
|
[Browsable (false)]
|
|
[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
|
|
public bool TodayDateSet {
|
|
get {
|
|
return today_date_set;
|
|
}
|
|
}
|
|
|
|
// the color used for trailing dates in the calendar
|
|
public Color TrailingForeColor {
|
|
set {
|
|
if (trailing_fore_color != value) {
|
|
trailing_fore_color = value;
|
|
SelectionRange bounds = this.GetDisplayRange (false);
|
|
SelectionRange visible_bounds = this.GetDisplayRange (true);
|
|
this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start));
|
|
this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End));
|
|
}
|
|
}
|
|
get {
|
|
return trailing_fore_color;
|
|
}
|
|
}
|
|
|
|
#endregion // Public Instance Properties
|
|
|
|
#region Protected Instance Properties
|
|
|
|
// overloaded to allow controll to be windowed for drop down
|
|
protected override CreateParams CreateParams {
|
|
get {
|
|
if (this.owner == null) {
|
|
return base.CreateParams;
|
|
} else {
|
|
CreateParams cp = base.CreateParams;
|
|
cp.Style ^= (int) WindowStyles.WS_CHILD;
|
|
cp.Style |= (int) WindowStyles.WS_POPUP;
|
|
cp.ExStyle |= (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
|
|
|
|
return cp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// not sure what to put in here - just doing a base() call - jba
|
|
protected override ImeMode DefaultImeMode {
|
|
get {
|
|
return base.DefaultImeMode;
|
|
}
|
|
}
|
|
|
|
protected override Padding DefaultMargin {
|
|
get {
|
|
return new Padding (9);
|
|
}
|
|
}
|
|
|
|
protected override Size DefaultSize {
|
|
get {
|
|
Size single_month = SingleMonthSize;
|
|
// get the width
|
|
int width = calendar_dimensions.Width * single_month.Width;
|
|
if (calendar_dimensions.Width > 1) {
|
|
width += (calendar_dimensions.Width - 1) * calendar_spacing.Width;
|
|
}
|
|
|
|
// get the height
|
|
int height = calendar_dimensions.Height * single_month.Height;
|
|
if (this.ShowToday) {
|
|
height += date_cell_size.Height + 2; // add the height of the "Today: " ...
|
|
}
|
|
if (calendar_dimensions.Height > 1) {
|
|
height += (calendar_dimensions.Height - 1) * calendar_spacing.Height;
|
|
}
|
|
|
|
// add the 1 pixel boundary
|
|
if (width > 0) {
|
|
width += 2;
|
|
}
|
|
if (height > 0) {
|
|
height +=2;
|
|
}
|
|
|
|
return new Size (width, height);
|
|
}
|
|
}
|
|
|
|
#endregion // Protected Instance Properties
|
|
|
|
#region Public Instance Methods
|
|
|
|
// add a date to the anually bolded date arraylist
|
|
public void AddAnnuallyBoldedDate (DateTime date) {
|
|
if (annually_bolded_dates == null)
|
|
annually_bolded_dates = new ArrayList ();
|
|
if (!annually_bolded_dates.Contains (date))
|
|
annually_bolded_dates.Add (date);
|
|
}
|
|
|
|
// add a date to the normal bolded date arraylist
|
|
public void AddBoldedDate (DateTime date) {
|
|
if (bolded_dates == null)
|
|
bolded_dates = new ArrayList ();
|
|
if (!bolded_dates.Contains (date))
|
|
bolded_dates.Add (date);
|
|
}
|
|
|
|
// add a date to the anually monthly date arraylist
|
|
public void AddMonthlyBoldedDate (DateTime date) {
|
|
if (monthly_bolded_dates == null)
|
|
monthly_bolded_dates = new ArrayList ();
|
|
if (!monthly_bolded_dates.Contains (date))
|
|
monthly_bolded_dates.Add (date);
|
|
}
|
|
|
|
// if visible = true, return only the dates of full months, else return all dates visible
|
|
public SelectionRange GetDisplayRange (bool visible) {
|
|
DateTime start;
|
|
DateTime end;
|
|
start = new DateTime (current_month.Year, current_month.Month, 1);
|
|
end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height);
|
|
end = end.AddDays(-1);
|
|
|
|
// process all visible dates if needed (including the grayed out dates
|
|
if (!visible) {
|
|
start = GetFirstDateInMonthGrid (start);
|
|
end = GetLastDateInMonthGrid (end);
|
|
}
|
|
|
|
return new SelectionRange (start, end);
|
|
}
|
|
|
|
// HitTest overload that recieve's x and y co-ordinates as separate ints
|
|
public HitTestInfo HitTest (int x, int y) {
|
|
return HitTest (new Point (x, y));
|
|
}
|
|
|
|
// returns a HitTestInfo for MonthCalendar element's under the specified point
|
|
public HitTestInfo HitTest (Point point) {
|
|
return HitTest (point, out last_clicked_calendar_index, out last_clicked_calendar_rect);
|
|
}
|
|
|
|
// clears all the annually bolded dates
|
|
public void RemoveAllAnnuallyBoldedDates () {
|
|
if (annually_bolded_dates != null)
|
|
annually_bolded_dates.Clear ();
|
|
}
|
|
|
|
// clears all the normal bolded dates
|
|
public void RemoveAllBoldedDates () {
|
|
if (bolded_dates != null)
|
|
bolded_dates.Clear ();
|
|
}
|
|
|
|
// clears all the monthly bolded dates
|
|
public void RemoveAllMonthlyBoldedDates () {
|
|
if (monthly_bolded_dates != null)
|
|
monthly_bolded_dates.Clear ();
|
|
}
|
|
|
|
// clears the specified annually bolded date (only compares day and month)
|
|
// only removes the first instance of the match
|
|
public void RemoveAnnuallyBoldedDate (DateTime date) {
|
|
if (annually_bolded_dates == null)
|
|
return;
|
|
|
|
for (int i = 0; i < annually_bolded_dates.Count; i++) {
|
|
DateTime dt = (DateTime) annually_bolded_dates [i];
|
|
if (dt.Day == date.Day && dt.Month == date.Month) {
|
|
annually_bolded_dates.RemoveAt (i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// clears all the normal bolded date
|
|
// only removes the first instance of the match
|
|
public void RemoveBoldedDate (DateTime date) {
|
|
if (bolded_dates == null)
|
|
return;
|
|
|
|
for (int i = 0; i < bolded_dates.Count; i++) {
|
|
DateTime dt = (DateTime) bolded_dates [i];
|
|
if (dt.Year == date.Year && dt.Month == date.Month && dt.Day == date.Day) {
|
|
bolded_dates.RemoveAt (i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// clears the specified monthly bolded date (only compares day and month)
|
|
// only removes the first instance of the match
|
|
public void RemoveMonthlyBoldedDate (DateTime date) {
|
|
if (monthly_bolded_dates == null)
|
|
return;
|
|
|
|
for (int i = 0; i < monthly_bolded_dates.Count; i++) {
|
|
DateTime dt = (DateTime) monthly_bolded_dates [i];
|
|
if (dt.Day == date.Day && dt.Month == date.Month) {
|
|
monthly_bolded_dates.RemoveAt (i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
|
|
public void SetCalendarDimensions(int x, int y) {
|
|
this.CalendarDimensions = new Size(x, y);
|
|
}
|
|
|
|
// sets the currently selected date as date
|
|
public void SetDate (DateTime date) {
|
|
this.SetSelectionRange (date.Date, date.Date);
|
|
}
|
|
|
|
// utility method set the SelectionRange property using individual dates
|
|
public void SetSelectionRange (DateTime date1, DateTime date2) {
|
|
this.SelectionRange = new SelectionRange(date1, date2);
|
|
}
|
|
|
|
public override string ToString () {
|
|
return this.GetType().Name + ", " + this.SelectionRange.ToString ();
|
|
}
|
|
|
|
// usually called after an AddBoldedDate method is called
|
|
// formats monthly and daily bolded dates according to the current calendar year
|
|
public void UpdateBoldedDates () {
|
|
Invalidate ();
|
|
}
|
|
|
|
#endregion // Public Instance Methods
|
|
|
|
#region Protected Instance Methods
|
|
|
|
// not sure why this needs to be overriden
|
|
protected override void CreateHandle () {
|
|
base.CreateHandle ();
|
|
}
|
|
|
|
// not sure why this needs to be overriden
|
|
protected override void Dispose (bool disposing) {
|
|
base.Dispose (disposing);
|
|
}
|
|
|
|
// Handle arrow keys
|
|
protected override bool IsInputKey (Keys keyData) {
|
|
switch (keyData) {
|
|
case Keys.Up:
|
|
case Keys.Down:
|
|
case Keys.Right:
|
|
case Keys.Left:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return base.IsInputKey (keyData);
|
|
}
|
|
|
|
// not sure why this needs to be overriden
|
|
protected override void OnBackColorChanged (EventArgs e) {
|
|
base.OnBackColorChanged (e);
|
|
this.Invalidate ();
|
|
}
|
|
|
|
// raises the date changed event
|
|
protected virtual void OnDateChanged (DateRangeEventArgs drevent) {
|
|
DateRangeEventHandler eh = (DateRangeEventHandler) (Events [DateChangedEvent]);
|
|
if (eh != null)
|
|
eh (this, drevent);
|
|
}
|
|
|
|
// raises the DateSelected event
|
|
protected virtual void OnDateSelected (DateRangeEventArgs drevent) {
|
|
DateRangeEventHandler eh = (DateRangeEventHandler) (Events [DateSelectedEvent]);
|
|
if (eh != null)
|
|
eh (this, drevent);
|
|
}
|
|
|
|
protected override void OnFontChanged (EventArgs e) {
|
|
// Update size based on new font's space requirements
|
|
Size = new Size (CalendarDimensions.Width * SingleMonthSize.Width,
|
|
CalendarDimensions.Height * SingleMonthSize.Height);
|
|
bold_font = new Font (Font, Font.Style | FontStyle.Bold);
|
|
base.OnFontChanged (e);
|
|
}
|
|
|
|
protected override void OnForeColorChanged (EventArgs e) {
|
|
base.OnForeColorChanged (e);
|
|
}
|
|
|
|
protected override void OnHandleCreated (EventArgs e) {
|
|
base.OnHandleCreated (e);
|
|
}
|
|
|
|
protected override void OnHandleDestroyed (EventArgs e)
|
|
{
|
|
base.OnHandleDestroyed (e);
|
|
}
|
|
|
|
[EditorBrowsable (EditorBrowsableState.Advanced)]
|
|
protected virtual void OnRightToLeftLayoutChanged (EventArgs e) {
|
|
EventHandler eh = (EventHandler) (Events [RightToLeftLayoutChangedEvent]);
|
|
if (eh != null)
|
|
eh (this, e);
|
|
}
|
|
|
|
// i think this is overriden to not allow the control to be changed to an arbitrary size
|
|
protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
|
|
{
|
|
// only allow sizes = default size to be set
|
|
Size default_size = DefaultSize;
|
|
Size min_size = default_size;
|
|
Size max_size = new Size (default_size.Width + SingleMonthSize.Width + calendar_spacing.Width,
|
|
default_size.Height + SingleMonthSize.Height + calendar_spacing.Height);
|
|
int x_mid_point = (max_size.Width + min_size.Width)/2;
|
|
int y_mid_point = (max_size.Height + min_size.Height)/2;
|
|
|
|
if (width < x_mid_point) {
|
|
width = min_size.Width;
|
|
} else {
|
|
width = max_size.Width;
|
|
}
|
|
if (height < y_mid_point) {
|
|
height = min_size.Height;
|
|
} else {
|
|
height = max_size.Height;
|
|
}
|
|
base.SetBoundsCore (x, y, width, height, specified);
|
|
}
|
|
|
|
protected override void WndProc (ref Message m) {
|
|
base.WndProc (ref m);
|
|
}
|
|
|
|
#endregion // Protected Instance Methods
|
|
|
|
#region public events
|
|
static object DateChangedEvent = new object ();
|
|
static object DateSelectedEvent = new object ();
|
|
static object RightToLeftLayoutChangedEvent = new object ();
|
|
|
|
// fired when the date is changed (either explicitely or implicitely)
|
|
// when navigating the month selector
|
|
public event DateRangeEventHandler DateChanged {
|
|
add { Events.AddHandler (DateChangedEvent, value); }
|
|
remove { Events.RemoveHandler (DateChangedEvent, value); }
|
|
}
|
|
|
|
// fired when the user explicitely clicks on date to select it
|
|
public event DateRangeEventHandler DateSelected {
|
|
add { Events.AddHandler (DateSelectedEvent, value); }
|
|
remove { Events.RemoveHandler (DateSelectedEvent, value); }
|
|
}
|
|
|
|
[Browsable(false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public new event EventHandler BackgroundImageChanged {
|
|
add { base.BackgroundImageChanged += value; }
|
|
remove { base.BackgroundImageChanged -= value; }
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public new event EventHandler BackgroundImageLayoutChanged
|
|
{
|
|
add { base.BackgroundImageLayoutChanged += value;}
|
|
remove { base.BackgroundImageLayoutChanged += value;}
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public new event EventHandler Click {
|
|
add {base.Click += value; }
|
|
remove {base.Click -= value;}
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public new event EventHandler DoubleClick {
|
|
add {base.DoubleClick += value; }
|
|
remove {base.DoubleClick -= value; }
|
|
}
|
|
|
|
[Browsable(false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public new event EventHandler ImeModeChanged {
|
|
add { base.ImeModeChanged += value; }
|
|
remove { base.ImeModeChanged -= value; }
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public new event MouseEventHandler MouseClick {
|
|
add { base.MouseClick += value;}
|
|
remove { base.MouseClick -= value;}
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public new event MouseEventHandler MouseDoubleClick {
|
|
add { base.MouseDoubleClick += value; }
|
|
remove { base.MouseDoubleClick -= value; }
|
|
}
|
|
|
|
[Browsable (false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public new event EventHandler PaddingChanged {
|
|
add {base.PaddingChanged += value;}
|
|
remove {base.PaddingChanged -= value;}
|
|
}
|
|
|
|
// XXX check this out
|
|
[Browsable(false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public new event PaintEventHandler Paint;
|
|
|
|
public event EventHandler RightToLeftLayoutChanged {
|
|
add {Events.AddHandler (RightToLeftLayoutChangedEvent, value);}
|
|
remove {Events.RemoveHandler (RightToLeftLayoutChangedEvent, value);}
|
|
}
|
|
|
|
[Browsable(false)]
|
|
[EditorBrowsable (EditorBrowsableState.Never)]
|
|
public new event EventHandler TextChanged {
|
|
add { base.TextChanged += value; }
|
|
remove { base.TextChanged -= value; }
|
|
}
|
|
#endregion // public events
|
|
|
|
#region internal properties
|
|
|
|
private void AddYears (int years, bool fast)
|
|
{
|
|
DateTime newDate;
|
|
if (fast) {
|
|
if (!(CurrentMonth.Year + years * 5 > MaxDate.Year)) {
|
|
newDate = CurrentMonth.AddYears (years * 5);
|
|
if (MaxDate >= newDate && MinDate <= newDate) {
|
|
CurrentMonth = newDate;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (!(CurrentMonth.Year + years > MaxDate.Year)) {
|
|
newDate = CurrentMonth.AddYears (years);
|
|
if (MaxDate >= newDate && MinDate <= newDate) {
|
|
CurrentMonth = newDate;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal bool IsYearGoingUp {
|
|
get {
|
|
return is_year_going_up;
|
|
}
|
|
set {
|
|
if (value) {
|
|
is_year_going_down = false;
|
|
year_moving_count = (is_year_going_up ? year_moving_count + 1 : 1);
|
|
if (is_year_going_up)
|
|
year_moving_count++;
|
|
else {
|
|
year_moving_count = 1;
|
|
}
|
|
AddYears (1, year_moving_count > 10);
|
|
if (is_mouse_moving_year)
|
|
StartHideTimer ();
|
|
} else {
|
|
year_moving_count = 0;
|
|
}
|
|
is_year_going_up = value;
|
|
Invalidate ();
|
|
}
|
|
}
|
|
|
|
internal bool IsYearGoingDown {
|
|
get {
|
|
return is_year_going_down;
|
|
}
|
|
set
|
|
{
|
|
if (value) {
|
|
is_year_going_up = false;
|
|
year_moving_count = (is_year_going_down ? year_moving_count + 1 : 1);
|
|
if (is_year_going_down)
|
|
year_moving_count++;
|
|
else {
|
|
year_moving_count = 1;
|
|
}
|
|
AddYears (-1, year_moving_count > 10);
|
|
if (is_mouse_moving_year)
|
|
StartHideTimer ();
|
|
} else {
|
|
year_moving_count = 0;
|
|
}
|
|
is_year_going_down = value;
|
|
Invalidate ();
|
|
}
|
|
}
|
|
|
|
internal bool ShowYearUpDown {
|
|
get {
|
|
return show_year_updown;
|
|
}
|
|
set {
|
|
if (show_year_updown != value) {
|
|
show_year_updown = value;
|
|
Invalidate ();
|
|
}
|
|
}
|
|
}
|
|
|
|
internal DateTime CurrentMonth {
|
|
set {
|
|
// only interested in if the month (not actual date) has change
|
|
if (value < MinDate || value > MaxDate) {
|
|
return;
|
|
}
|
|
|
|
if (value.Month != current_month.Month ||
|
|
value.Year != current_month.Year) {
|
|
this.SelectionRange = new SelectionRange(
|
|
this.SelectionStart.Add(value.Subtract(current_month)),
|
|
this.SelectionEnd.Add(value.Subtract(current_month)));
|
|
current_month = value;
|
|
UpdateBoldedDates();
|
|
this.Invalidate();
|
|
}
|
|
}
|
|
get {
|
|
return current_month;
|
|
}
|
|
}
|
|
|
|
#endregion // internal properties
|
|
|
|
#region internal/private methods
|
|
internal HitTestInfo HitTest (
|
|
Point point,
|
|
out int calendar_index,
|
|
out Rectangle calendar_rect) {
|
|
// start by initialising the ref parameters
|
|
calendar_index = -1;
|
|
calendar_rect = Rectangle.Empty;
|
|
|
|
// before doing all the hard work, see if the today's date wasn't clicked
|
|
Rectangle today_rect = new Rectangle (
|
|
ClientRectangle.X,
|
|
ClientRectangle.Bottom - date_cell_size.Height,
|
|
7 * date_cell_size.Width,
|
|
date_cell_size.Height);
|
|
if (today_rect.Contains (point) && this.ShowToday) {
|
|
return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now);
|
|
}
|
|
|
|
Size month_size = SingleMonthSize;
|
|
// define calendar rect's that this thing can land in
|
|
Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height];
|
|
for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) {
|
|
if (i == 0) {
|
|
calendars[i] = new Rectangle (
|
|
new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1),
|
|
month_size);
|
|
} else {
|
|
// calendar on the next row
|
|
if (i % CalendarDimensions.Width == 0) {
|
|
calendars[i] = new Rectangle (
|
|
new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height),
|
|
month_size);
|
|
} else {
|
|
// calendar on the next column
|
|
calendars[i] = new Rectangle (
|
|
new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y),
|
|
month_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
// through each trying to find a match
|
|
for (int i = 0; i < calendars.Length ; i++) {
|
|
if (calendars[i].Contains (point)) {
|
|
// check the title section
|
|
Rectangle title_rect = new Rectangle (
|
|
calendars[i].Location,
|
|
title_size);
|
|
if (title_rect.Contains (point) ) {
|
|
// make sure it's not a previous button
|
|
if (i == 0) {
|
|
Rectangle button_rect = new Rectangle(
|
|
new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2),
|
|
button_size);
|
|
if (button_rect.Contains (point)) {
|
|
return new HitTestInfo (HitArea.PrevMonthButton, point, new DateTime (1, 1, 1));
|
|
}
|
|
}
|
|
// make sure it's not the next button
|
|
if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) {
|
|
Rectangle button_rect = new Rectangle(
|
|
new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2),
|
|
button_size);
|
|
if (button_rect.Contains (point)) {
|
|
return new HitTestInfo (HitArea.NextMonthButton, point, new DateTime (1, 1, 1));
|
|
}
|
|
}
|
|
|
|
// indicate which calendar and month it was
|
|
calendar_index = i;
|
|
calendar_rect = calendars[i];
|
|
|
|
// make sure it's not the month or the year of the calendar
|
|
if (GetMonthNameRectangle (title_rect, i).Contains (point)) {
|
|
return new HitTestInfo (HitArea.TitleMonth, point, new DateTime (1, 1, 1));
|
|
}
|
|
Rectangle year, up, down;
|
|
GetYearNameRectangles (title_rect, i, out year, out up, out down);
|
|
if (year.Contains (point)) {
|
|
return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.YearRectangle);
|
|
} else if (up.Contains (point)) {
|
|
return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.UpButton);
|
|
} else if (down.Contains (point)) {
|
|
return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.DownButton);
|
|
}
|
|
|
|
// return the hit test in the title background
|
|
return new HitTestInfo (HitArea.TitleBackground, point, new DateTime (1, 1, 1));
|
|
}
|
|
|
|
Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom);
|
|
|
|
// see if it's in the Week numbers
|
|
if (ShowWeekNumbers) {
|
|
Rectangle weeks_rect = new Rectangle (
|
|
date_grid_location,
|
|
new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0)));
|
|
if (weeks_rect.Contains (point)) {
|
|
return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now);
|
|
}
|
|
|
|
// move the location of the grid over
|
|
date_grid_location.X += date_cell_size.Width;
|
|
}
|
|
|
|
// see if it's in the week names
|
|
Rectangle day_rect = new Rectangle (
|
|
date_grid_location,
|
|
new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height));
|
|
if (day_rect.Contains (point)) {
|
|
return new HitTestInfo (HitArea.DayOfWeek, point, new DateTime (1, 1, 1));
|
|
}
|
|
|
|
// finally see if it was a date that was clicked
|
|
Rectangle date_grid = new Rectangle (
|
|
new Point (day_rect.X, day_rect.Bottom),
|
|
new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0)));
|
|
if (date_grid.Contains (point)) {
|
|
clicked_rect = date_grid;
|
|
// okay so it's inside the grid, get the offset
|
|
Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y);
|
|
int row = offset.Y / date_cell_size.Height;
|
|
int col = offset.X / date_cell_size.Width;
|
|
// establish our first day of the month
|
|
DateTime calendar_month = this.CurrentMonth.AddMonths(i);
|
|
DateTime first_day = GetFirstDateInMonthGrid (calendar_month);
|
|
DateTime time = first_day.AddDays ((row * 7) + col);
|
|
// establish which date was clicked
|
|
if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) {
|
|
if (time < calendar_month && i == 0) {
|
|
return new HitTestInfo (HitArea.PrevMonthDate, point, new DateTime (1, 1, 1), time);
|
|
} else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) {
|
|
return new HitTestInfo (HitArea.NextMonthDate, point, new DateTime (1, 1, 1), time);
|
|
}
|
|
return new HitTestInfo (HitArea.Nowhere, point, new DateTime (1, 1, 1));
|
|
}
|
|
return new HitTestInfo(HitArea.Date, point, time);
|
|
}
|
|
}
|
|
}
|
|
|
|
return new HitTestInfo ();
|
|
}
|
|
|
|
// returns the date of the first cell of the specified month grid
|
|
internal DateTime GetFirstDateInMonthGrid (DateTime month) {
|
|
// convert the first_day_of_week into a DayOfWeekEnum
|
|
DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
|
|
// find the first day of the month
|
|
DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1);
|
|
DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek;
|
|
// adjust for the starting day of the week
|
|
int offset = first_day_of_month - first_day;
|
|
if (offset < 0) {
|
|
offset += 7;
|
|
}
|
|
return first_date_of_month.AddDays (-1*offset);
|
|
}
|
|
|
|
// returns the date of the last cell of the specified month grid
|
|
internal DateTime GetLastDateInMonthGrid (DateTime month)
|
|
{
|
|
DateTime start = GetFirstDateInMonthGrid(month);
|
|
return start.AddDays ((7 * 6)-1);
|
|
}
|
|
|
|
internal bool IsBoldedDate (DateTime date) {
|
|
// check bolded dates
|
|
if (bolded_dates != null && bolded_dates.Count > 0) {
|
|
foreach (DateTime bolded_date in bolded_dates) {
|
|
if (bolded_date.Date == date.Date) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
// check monthly dates
|
|
if (monthly_bolded_dates != null && monthly_bolded_dates.Count > 0) {
|
|
foreach (DateTime bolded_date in monthly_bolded_dates) {
|
|
if (bolded_date.Day == date.Day) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
// check yearly dates
|
|
if (annually_bolded_dates != null && annually_bolded_dates.Count > 0) {
|
|
foreach (DateTime bolded_date in annually_bolded_dates) {
|
|
if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false; // no match
|
|
}
|
|
|
|
// initialise the 'go to today' context menu
|
|
private void SetUpTodayMenu () {
|
|
today_menu = new ContextMenu ();
|
|
MenuItem menu_item = new MenuItem ("Go to today");
|
|
menu_item.Click += new EventHandler (TodayMenuItemClickHandler);
|
|
today_menu.MenuItems.Add (menu_item);
|
|
}
|
|
|
|
// initialise the month context menu
|
|
private void SetUpMonthMenu () {
|
|
month_menu = new ContextMenu ();
|
|
for (int i=0; i < 12; i++) {
|
|
MenuItem menu_item = new MenuItem ( new DateTime (2000, i+1, 1).ToString ("MMMM"));
|
|
menu_item.Click += new EventHandler (MonthMenuItemClickHandler);
|
|
month_menu.MenuItems.Add (menu_item);
|
|
}
|
|
}
|
|
|
|
// returns the first date of the month
|
|
private DateTime GetFirstDateInMonth (DateTime date) {
|
|
return new DateTime (date.Year, date.Month, 1);
|
|
}
|
|
|
|
// returns the last date of the month
|
|
private DateTime GetLastDateInMonth (DateTime date) {
|
|
return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
|
|
}
|
|
|
|
// called in response to users seletion with shift key
|
|
private void AddTimeToSelection (int delta, bool isDays)
|
|
{
|
|
DateTime cursor_point;
|
|
DateTime end_point;
|
|
// okay we add the period to the date that is not the same as the
|
|
// start date when shift was first clicked.
|
|
if (SelectionStart != first_select_start_date) {
|
|
cursor_point = SelectionStart;
|
|
} else {
|
|
cursor_point = SelectionEnd;
|
|
}
|
|
// add the days
|
|
if (isDays) {
|
|
end_point = cursor_point.AddDays (delta);
|
|
} else {
|
|
// delta must be months
|
|
end_point = cursor_point.AddMonths (delta);
|
|
}
|
|
// set the new selection range
|
|
SelectionRange range = new SelectionRange (first_select_start_date, end_point);
|
|
if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
|
|
// okay the date is beyond what is allowed, lets set the maximum we can
|
|
if (range.Start != first_select_start_date) {
|
|
range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
|
|
} else {
|
|
range.End = range.Start.AddDays (MaxSelectionCount-1);
|
|
}
|
|
}
|
|
|
|
// Avoid re-setting SelectionRange to the same value and fire an extra DateChanged event
|
|
if (range.Start != selection_range.Start || range.End != selection_range.End)
|
|
SelectionRange = range;
|
|
}
|
|
|
|
// attempts to add the date to the selection without throwing exception
|
|
private void SelectDate (DateTime date) {
|
|
// try and add the new date to the selction range
|
|
SelectionRange range = null;
|
|
if (is_shift_pressed || (click_state [0])) {
|
|
range = new SelectionRange (first_select_start_date, date);
|
|
if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
|
|
// okay the date is beyond what is allowed, lets set the maximum we can
|
|
if (range.Start != first_select_start_date) {
|
|
range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
|
|
} else {
|
|
range.End = range.Start.AddDays (MaxSelectionCount-1);
|
|
}
|
|
}
|
|
} else {
|
|
if (date >= MinDate && date <= MaxDate) {
|
|
range = new SelectionRange (date, date);
|
|
first_select_start_date = date;
|
|
}
|
|
}
|
|
|
|
// Only set if we re actually getting a different range (avoid an extra DateChanged event)
|
|
if (range != null && range.Start != selection_range.Start || range.End != selection_range.End)
|
|
SelectionRange = range;
|
|
}
|
|
|
|
// gets the week of the year
|
|
internal int GetWeekOfYear (DateTime date) {
|
|
// convert the first_day_of_week into a DayOfWeekEnum
|
|
DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
|
|
// find the first day of the year
|
|
DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek;
|
|
// adjust for the starting day of the week
|
|
int offset = first_day_of_year - first_day;
|
|
int week = ((date.DayOfYear + offset) / 7) + 1;
|
|
return week;
|
|
}
|
|
|
|
// convert a Day enum into a DayOfWeek enum
|
|
internal DayOfWeek GetDayOfWeek (Day day) {
|
|
if (day == Day.Default) {
|
|
return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
|
|
} else {
|
|
return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ());
|
|
}
|
|
}
|
|
|
|
// returns the rectangle for themonth name
|
|
internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) {
|
|
DateTime this_month = this.current_month.AddMonths (calendar_index);
|
|
Size title_text_size = TextRenderer.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
|
|
Size month_size = TextRenderer.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
|
|
// return only the month name part of that
|
|
return new Rectangle (
|
|
new Point (
|
|
title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
|
|
title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
|
|
month_size);
|
|
}
|
|
|
|
internal void GetYearNameRectangles (Rectangle title_rect, int calendar_index, out Rectangle year_rect, out Rectangle up_rect, out Rectangle down_rect)
|
|
{
|
|
DateTime this_month = this.current_month.AddMonths (calendar_index);
|
|
SizeF title_text_size = TextRenderer.MeasureString (this_month.ToString ("MMMM yyyy"), this.bold_font, int.MaxValue, centered_format);
|
|
SizeF year_size = TextRenderer.MeasureString (this_month.ToString ("yyyy"), this.bold_font, int.MaxValue, centered_format);
|
|
// find out how much space the title took
|
|
RectangleF text_rect = new RectangleF (
|
|
new PointF (
|
|
title_rect.X + ((title_rect.Width - title_text_size.Width) / 2),
|
|
title_rect.Y + ((title_rect.Height - title_text_size.Height) / 2)),
|
|
title_text_size);
|
|
// return only the rect of the year
|
|
year_rect = new Rectangle (
|
|
new Point (
|
|
((int)(text_rect.Right - year_size.Width + 1)),
|
|
(int)text_rect.Y),
|
|
new Size ((int)(year_size.Width + 1), (int)(year_size.Height + 1)));
|
|
|
|
year_rect.Inflate (0, 1);
|
|
up_rect = new Rectangle ();
|
|
up_rect.Location = new Point (year_rect.X + year_rect.Width + 2, year_rect.Y);
|
|
up_rect.Size = new Size (16, year_rect.Height / 2);
|
|
down_rect = new Rectangle ();
|
|
down_rect.Location = new Point (up_rect.X, up_rect.Y + up_rect.Height + 1);
|
|
down_rect.Size = up_rect.Size;
|
|
}
|
|
|
|
// returns the rectangle for the year in the title
|
|
internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {
|
|
Rectangle result, discard;
|
|
GetYearNameRectangles (title_rect, calendar_index, out result, out discard, out discard);
|
|
return result;
|
|
}
|
|
|
|
// determine if date is allowed to be drawn in month
|
|
internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
|
|
DateTime tocheck = month.AddMonths (-1);
|
|
if ((month.Year == date.Year && month.Month == date.Month) ||
|
|
(tocheck.Year == date.Year && tocheck.Month == date.Month)) {
|
|
return true;
|
|
}
|
|
|
|
// check the railing dates (days in the month after the last month in grid)
|
|
if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
|
|
tocheck = month.AddMonths (1);
|
|
return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// set one item clicked and all others off
|
|
private void SetItemClick(HitTestInfo hti)
|
|
{
|
|
switch(hti.HitArea) {
|
|
case HitArea.NextMonthButton:
|
|
this.is_previous_clicked = false;
|
|
this.is_next_clicked = true;
|
|
this.is_date_clicked = false;
|
|
break;
|
|
case HitArea.PrevMonthButton:
|
|
this.is_previous_clicked = true;
|
|
this.is_next_clicked = false;
|
|
this.is_date_clicked = false;
|
|
break;
|
|
case HitArea.PrevMonthDate:
|
|
case HitArea.NextMonthDate:
|
|
case HitArea.Date:
|
|
this.clicked_date = hti.hit_time;
|
|
this.is_previous_clicked = false;
|
|
this.is_next_clicked = false;
|
|
this.is_date_clicked = true;
|
|
break;
|
|
default :
|
|
this.is_previous_clicked = false;
|
|
this.is_next_clicked = false;
|
|
this.is_date_clicked = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// called when today context menu is clicked
|
|
private void TodayMenuItemClickHandler (object sender, EventArgs e)
|
|
{
|
|
this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
|
|
this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
|
|
}
|
|
|
|
// called when month context menu is clicked
|
|
private void MonthMenuItemClickHandler (object sender, EventArgs e) {
|
|
MenuItem item = sender as MenuItem;
|
|
if (item != null && month_title_click_location != Point.Empty) {
|
|
// establish which month we want to move to
|
|
if (item.Parent == null) {
|
|
return;
|
|
}
|
|
int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
|
|
if (new_month == 0) {
|
|
return;
|
|
}
|
|
// okay let's establish which calendar was hit
|
|
Size month_size = this.SingleMonthSize;
|
|
for (int i=0; i < CalendarDimensions.Height; i++) {
|
|
for (int j=0; j < CalendarDimensions.Width; j++) {
|
|
int month_index = (i * CalendarDimensions.Width) + j;
|
|
Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
|
|
if (j == 0) {
|
|
month_rect.X = this.ClientRectangle.X + 1;
|
|
} else {
|
|
month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
|
|
}
|
|
if (i == 0) {
|
|
month_rect.Y = this.ClientRectangle.Y + 1;
|
|
} else {
|
|
month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
|
|
}
|
|
// see if the point is inside
|
|
if (month_rect.Contains (month_title_click_location)) {
|
|
DateTime clicked_month = CurrentMonth.AddMonths (month_index);
|
|
// get the month that we want to move to
|
|
int month_offset = new_month - clicked_month.Month;
|
|
|
|
// move forward however more months we need to
|
|
this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// clear the point
|
|
month_title_click_location = Point.Empty;
|
|
}
|
|
}
|
|
|
|
// raised on the timer, for mouse hold clicks
|
|
private void TimerHandler (object sender, EventArgs e) {
|
|
// now find out which area was click
|
|
if (this.Capture) {
|
|
HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
|
|
// see if it was clicked on the prev or next mouse
|
|
if (click_state [1] || click_state [2]) {
|
|
// invalidate the area where the mouse was last held
|
|
DoMouseUp ();
|
|
// register the click
|
|
if (hti.HitArea == HitArea.PrevMonthButton ||
|
|
hti.HitArea == HitArea.NextMonthButton) {
|
|
DoButtonMouseDown (hti);
|
|
click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
|
|
click_state [2] = !click_state [1];
|
|
}
|
|
if (timer.Interval != 300) {
|
|
timer.Interval = 300;
|
|
}
|
|
}
|
|
} else {
|
|
timer.Enabled = false;
|
|
}
|
|
}
|
|
|
|
// selects one of the buttons
|
|
private void DoButtonMouseDown (HitTestInfo hti) {
|
|
// show the click then move on
|
|
SetItemClick(hti);
|
|
if (hti.HitArea == HitArea.PrevMonthButton) {
|
|
// invalidate the prev monthbutton
|
|
this.Invalidate(
|
|
new Rectangle (
|
|
this.ClientRectangle.X + 1 + button_x_offset,
|
|
this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
|
|
button_size.Width,
|
|
button_size.Height));
|
|
int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change);
|
|
this.CurrentMonth = this.CurrentMonth.AddMonths (-scroll);
|
|
} else {
|
|
// invalidate the next monthbutton
|
|
this.Invalidate(
|
|
new Rectangle (
|
|
this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
|
|
this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
|
|
button_size.Width,
|
|
button_size.Height));
|
|
int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change);
|
|
this.CurrentMonth = this.CurrentMonth.AddMonths (scroll);
|
|
}
|
|
}
|
|
|
|
// selects the clicked date
|
|
private void DoDateMouseDown (HitTestInfo hti) {
|
|
SetItemClick(hti);
|
|
}
|
|
|
|
// event run on the mouse up event
|
|
private void DoMouseUp () {
|
|
|
|
IsYearGoingDown = false;
|
|
IsYearGoingUp = false;
|
|
is_mouse_moving_year = false;
|
|
|
|
// invalidate the next monthbutton
|
|
if (this.is_next_clicked) {
|
|
this.Invalidate(
|
|
new Rectangle (
|
|
this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
|
|
this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
|
|
button_size.Width,
|
|
button_size.Height));
|
|
}
|
|
// invalidate the prev monthbutton
|
|
if (this.is_previous_clicked) {
|
|
this.Invalidate(
|
|
new Rectangle (
|
|
this.ClientRectangle.X + 1 + button_x_offset,
|
|
this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
|
|
button_size.Width,
|
|
button_size.Height));
|
|
}
|
|
if (this.is_date_clicked) {
|
|
// invalidate the area under the cursor, to remove focus rect
|
|
this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));
|
|
}
|
|
this.is_previous_clicked = false;
|
|
this.is_next_clicked = false;
|
|
this.is_date_clicked = false;
|
|
}
|
|
|
|
// needed when in windowed mode to close the calendar if no
|
|
// part of it has focus.
|
|
private void UpDownTimerTick(object sender, EventArgs e)
|
|
{
|
|
if (IsYearGoingUp) {
|
|
IsYearGoingUp = true;
|
|
}
|
|
if (IsYearGoingDown) {
|
|
IsYearGoingDown = true;
|
|
}
|
|
|
|
if (!IsYearGoingDown && !IsYearGoingUp) {
|
|
updown_timer.Enabled = false;
|
|
} else if (IsYearGoingDown || IsYearGoingUp) {
|
|
updown_timer.Interval = subsequent_delay;
|
|
}
|
|
}
|
|
|
|
// Needed when in windowed mode.
|
|
private void StartHideTimer ()
|
|
{
|
|
if (updown_timer == null) {
|
|
updown_timer = new Timer ();
|
|
updown_timer.Tick += new EventHandler (UpDownTimerTick);
|
|
}
|
|
updown_timer.Interval = initial_delay;
|
|
updown_timer.Enabled = true;
|
|
}
|
|
|
|
// occurs when mouse moves around control, used for selection
|
|
private void MouseMoveHandler (object sender, MouseEventArgs e) {
|
|
HitTestInfo hti = this.HitTest (e.X, e.Y);
|
|
// clear the last clicked item
|
|
if (click_state [0]) {
|
|
// register the click
|
|
if (hti.HitArea == HitArea.PrevMonthDate ||
|
|
hti.HitArea == HitArea.NextMonthDate ||
|
|
hti.HitArea == HitArea.Date)
|
|
{
|
|
Rectangle prev_rect = clicked_rect;
|
|
DateTime prev_clicked = clicked_date;
|
|
DoDateMouseDown (hti);
|
|
if (owner == null) {
|
|
click_state [0] = true;
|
|
} else {
|
|
click_state [0] = false;
|
|
click_state [1] = false;
|
|
click_state [2] = false;
|
|
}
|
|
|
|
if (prev_clicked != clicked_date) {
|
|
// select date after updating click_state and clicked_date
|
|
SelectDate (clicked_date);
|
|
date_selected_event_pending = true;
|
|
|
|
Rectangle invalid = Rectangle.Union (prev_rect, clicked_rect);
|
|
Invalidate (invalid);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// to check if the mouse has come down on this control
|
|
private void MouseDownHandler (object sender, MouseEventArgs e)
|
|
{
|
|
if ((e.Button & MouseButtons.Left) == 0)
|
|
return;
|
|
|
|
// clear the click_state variables
|
|
click_state [0] = false;
|
|
click_state [1] = false;
|
|
click_state [2] = false;
|
|
|
|
// disable the timer if it was enabled
|
|
if (timer.Enabled) {
|
|
timer.Stop ();
|
|
timer.Enabled = false;
|
|
}
|
|
|
|
Point point = new Point (e.X, e.Y);
|
|
// figure out if we are in drop down mode and a click happened outside us
|
|
if (this.owner != null) {
|
|
if (!this.ClientRectangle.Contains (point)) {
|
|
this.owner.HideMonthCalendar ();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//establish where was hit
|
|
HitTestInfo hti = this.HitTest(point);
|
|
// hide the year numeric up down if it was clicked
|
|
if (ShowYearUpDown && hti.HitArea != HitArea.TitleYear) {
|
|
ShowYearUpDown = false;
|
|
}
|
|
switch (hti.HitArea) {
|
|
case HitArea.PrevMonthButton:
|
|
case HitArea.NextMonthButton:
|
|
DoButtonMouseDown (hti);
|
|
click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
|
|
click_state [2] = !click_state [1];
|
|
timer.Interval = 750;
|
|
timer.Start ();
|
|
break;
|
|
case HitArea.Date:
|
|
case HitArea.PrevMonthDate:
|
|
case HitArea.NextMonthDate:
|
|
DoDateMouseDown (hti);
|
|
|
|
// select date before updating click_state
|
|
SelectDate (clicked_date);
|
|
date_selected_event_pending = true;
|
|
|
|
// leave clicked state blank if drop down window
|
|
if (owner == null) {
|
|
click_state [0] = true;
|
|
} else {
|
|
click_state [0] = false;
|
|
click_state [1] = false;
|
|
click_state [2] = false;
|
|
}
|
|
|
|
break;
|
|
case HitArea.TitleMonth:
|
|
month_title_click_location = hti.Point;
|
|
month_menu.Show (this, hti.Point);
|
|
if (this.Capture && owner != null) {
|
|
Capture = false;
|
|
Capture = true;
|
|
}
|
|
break;
|
|
case HitArea.TitleYear:
|
|
// place the numeric up down
|
|
if (ShowYearUpDown) {
|
|
if (hti.hit_area_extra == HitAreaExtra.UpButton) {
|
|
is_mouse_moving_year = true;
|
|
IsYearGoingUp = true;
|
|
} else if (hti.hit_area_extra == HitAreaExtra.DownButton) {
|
|
is_mouse_moving_year = true;
|
|
IsYearGoingDown = true;
|
|
}
|
|
return;
|
|
} else {
|
|
ShowYearUpDown = true;
|
|
}
|
|
break;
|
|
case HitArea.TodayLink:
|
|
this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
|
|
this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
|
|
break;
|
|
default:
|
|
this.is_previous_clicked = false;
|
|
this.is_next_clicked = false;
|
|
this.is_date_clicked = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// raised by any key down events
|
|
private void KeyDownHandler (object sender, KeyEventArgs e) {
|
|
// send keys to the year_updown control, let it handle it
|
|
if(ShowYearUpDown) {
|
|
switch (e.KeyCode) {
|
|
case Keys.Enter:
|
|
ShowYearUpDown = false;
|
|
IsYearGoingDown = false;
|
|
IsYearGoingUp = false;
|
|
break;
|
|
case Keys.Up: {
|
|
IsYearGoingUp = true;
|
|
break;
|
|
}
|
|
case Keys.Down: {
|
|
IsYearGoingDown = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (!is_shift_pressed && e.Shift) {
|
|
first_select_start_date = SelectionStart;
|
|
is_shift_pressed = e.Shift;
|
|
e.Handled = true;
|
|
}
|
|
switch (e.KeyCode) {
|
|
case Keys.Home:
|
|
// set the date to the start of the month
|
|
if (is_shift_pressed) {
|
|
DateTime date = GetFirstDateInMonth (first_select_start_date);
|
|
if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
|
|
date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
|
|
}
|
|
this.SetSelectionRange (date, first_select_start_date);
|
|
} else {
|
|
DateTime date = GetFirstDateInMonth (this.SelectionStart);
|
|
this.SetSelectionRange (date, date);
|
|
}
|
|
e.Handled = true;
|
|
break;
|
|
case Keys.End:
|
|
// set the date to the last of the month
|
|
if (is_shift_pressed) {
|
|
DateTime date = GetLastDateInMonth (first_select_start_date);
|
|
if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
|
|
date = first_select_start_date.AddDays (MaxSelectionCount-1);
|
|
}
|
|
this.SetSelectionRange (date, first_select_start_date);
|
|
} else {
|
|
DateTime date = GetLastDateInMonth (this.SelectionStart);
|
|
this.SetSelectionRange (date, date);
|
|
}
|
|
e.Handled = true;
|
|
break;
|
|
case Keys.PageUp:
|
|
// set the date to the last of the month
|
|
if (is_shift_pressed) {
|
|
this.AddTimeToSelection (-1, false);
|
|
} else {
|
|
DateTime date = this.SelectionStart.AddMonths (-1);
|
|
this.SetSelectionRange (date, date);
|
|
}
|
|
e.Handled = true;
|
|
break;
|
|
case Keys.PageDown:
|
|
// set the date to the last of the month
|
|
if (is_shift_pressed) {
|
|
this.AddTimeToSelection (1, false);
|
|
} else {
|
|
DateTime date = this.SelectionStart.AddMonths (1);
|
|
this.SetSelectionRange (date, date);
|
|
}
|
|
e.Handled = true;
|
|
break;
|
|
case Keys.Up:
|
|
// set the back 1 week
|
|
if (is_shift_pressed) {
|
|
this.AddTimeToSelection (-7, true);
|
|
} else {
|
|
DateTime date = this.SelectionStart.AddDays (-7);
|
|
this.SetSelectionRange (date, date);
|
|
}
|
|
e.Handled = true;
|
|
break;
|
|
case Keys.Down:
|
|
// set the date forward 1 week
|
|
if (is_shift_pressed) {
|
|
this.AddTimeToSelection (7, true);
|
|
} else {
|
|
DateTime date = this.SelectionStart.AddDays (7);
|
|
this.SetSelectionRange (date, date);
|
|
}
|
|
e.Handled = true;
|
|
break;
|
|
case Keys.Left:
|
|
// move one left
|
|
if (is_shift_pressed) {
|
|
this.AddTimeToSelection (-1, true);
|
|
} else {
|
|
DateTime date = this.SelectionStart.AddDays (-1);
|
|
this.SetSelectionRange (date, date);
|
|
}
|
|
e.Handled = true;
|
|
break;
|
|
case Keys.Right:
|
|
// move one left
|
|
if (is_shift_pressed) {
|
|
this.AddTimeToSelection (1, true);
|
|
} else {
|
|
DateTime date = this.SelectionStart.AddDays (1);
|
|
this.SetSelectionRange (date, date);
|
|
}
|
|
e.Handled = true;
|
|
break;
|
|
case Keys.F4:
|
|
// Close ourselves on Alt-F4 if we are a popup
|
|
if (e.Alt && owner != null) {
|
|
this.Hide ();
|
|
e.Handled = true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// to check if the mouse has come up on this control
|
|
private void MouseUpHandler (object sender, MouseEventArgs e)
|
|
{
|
|
if ((e.Button & MouseButtons.Left) == 0) {
|
|
if (show_today && (this.ContextMenu == null))
|
|
today_menu.Show (this, new Point (e.X, e.Y));
|
|
return;
|
|
}
|
|
|
|
if (timer.Enabled) {
|
|
timer.Stop ();
|
|
}
|
|
// clear the click state array
|
|
click_state [0] = false;
|
|
click_state [1] = false;
|
|
click_state [2] = false;
|
|
// do the regulare mouseup stuff
|
|
this.DoMouseUp ();
|
|
|
|
if (date_selected_event_pending) {
|
|
OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
|
|
date_selected_event_pending = false;
|
|
}
|
|
}
|
|
|
|
// raised by any key up events
|
|
private void KeyUpHandler (object sender, KeyEventArgs e) {
|
|
is_shift_pressed = e.Shift ;
|
|
e.Handled = true;
|
|
IsYearGoingUp = false;
|
|
IsYearGoingDown = false;
|
|
}
|
|
|
|
// paint this control now
|
|
private void PaintHandler (object sender, PaintEventArgs pe) {
|
|
if (Width <= 0 || Height <= 0 || Visible == false)
|
|
return;
|
|
|
|
Draw (pe.ClipRectangle, pe.Graphics);
|
|
|
|
// fire the new paint handler
|
|
if (this.Paint != null)
|
|
{
|
|
this.Paint (sender, pe);
|
|
}
|
|
}
|
|
|
|
// returns the region of the control that needs to be redrawn
|
|
private void InvalidateDateRange (SelectionRange range) {
|
|
SelectionRange bounds = this.GetDisplayRange (false);
|
|
|
|
if (range.End < bounds.Start || range.Start > bounds.End) {
|
|
// don't invalidate anything, as the modified date range
|
|
// is outside the visible bounds of this control
|
|
return;
|
|
}
|
|
// adjust the start and end to be inside the visible range
|
|
if (range.Start < bounds.Start) {
|
|
range = new SelectionRange (bounds.Start, range.End);
|
|
}
|
|
if (range.End > bounds.End) {
|
|
range = new SelectionRange (range.Start, bounds.End);
|
|
}
|
|
// now invalidate the date rectangles as series of rows
|
|
DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
|
|
DateTime current = range.Start;
|
|
while (current <= range.End) {
|
|
DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
|
|
Rectangle start_rect;
|
|
Rectangle end_rect;
|
|
// see if entire selection is in this current month
|
|
if (range.End <= month_end && current < last_month) {
|
|
// the end is the last date
|
|
if (current < this.current_month) {
|
|
start_rect = GetDateRowRect (current_month, current_month);
|
|
} else {
|
|
start_rect = GetDateRowRect (current, current);
|
|
}
|
|
end_rect = GetDateRowRect (current, range.End);
|
|
} else if (current < last_month) {
|
|
// otherwise it simply means we have a selection spaning
|
|
// multiple months simply set rectangle inside the current month
|
|
start_rect = GetDateRowRect (current, current);
|
|
end_rect = GetDateRowRect (month_end, month_end);
|
|
} else {
|
|
// it's outside the visible range
|
|
start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
|
|
end_rect = GetDateRowRect (last_month, range.End);
|
|
}
|
|
// push to the next month
|
|
current = month_end.AddDays (1);
|
|
// invalidate from the start row to the end row for this month
|
|
this.Invalidate (
|
|
new Rectangle (
|
|
start_rect.X,
|
|
start_rect.Y,
|
|
start_rect.Width,
|
|
Math.Max (end_rect.Bottom - start_rect.Y, 0)));
|
|
}
|
|
}
|
|
|
|
// gets the rect of the row where the specified date appears on the specified month
|
|
private Rectangle GetDateRowRect (DateTime month, DateTime date) {
|
|
// first get the general rect of the supplied month
|
|
Size month_size = SingleMonthSize;
|
|
Rectangle month_rect = Rectangle.Empty;
|
|
for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
|
|
DateTime this_month = this.current_month.AddMonths (i);
|
|
if (month.Year == this_month.Year && month.Month == this_month.Month) {
|
|
month_rect = new Rectangle (
|
|
this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
|
|
this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
|
|
month_size.Width,
|
|
month_size.Height);
|
|
break;
|
|
}
|
|
}
|
|
// now find out where in the month the supplied date is
|
|
if (month_rect == Rectangle.Empty) {
|
|
return Rectangle.Empty;
|
|
}
|
|
// find out which row this date is in
|
|
int row = -1;
|
|
DateTime first_date = GetFirstDateInMonthGrid (month);
|
|
DateTime end_date = first_date.AddDays (7);
|
|
for (int i=0; i < 6; i++) {
|
|
if (date >= first_date && date < end_date) {
|
|
row = i;
|
|
break;
|
|
}
|
|
first_date = end_date;
|
|
end_date = end_date.AddDays (7);
|
|
}
|
|
// ensure it's a valid row
|
|
if (row < 0) {
|
|
return Rectangle.Empty;
|
|
}
|
|
int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
|
|
int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
|
|
return new Rectangle (
|
|
month_rect.X + x_offset,
|
|
month_rect.Y + y_offset,
|
|
date_cell_size.Width * 7,
|
|
date_cell_size.Height);
|
|
}
|
|
|
|
internal void Draw (Rectangle clip_rect, Graphics dc)
|
|
{
|
|
ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this);
|
|
}
|
|
|
|
internal override bool InternalCapture {
|
|
get {
|
|
return base.InternalCapture;
|
|
}
|
|
set {
|
|
// Don't allow internal capture when DateTimePicker is using us
|
|
// Control sets this on MouseDown
|
|
if (owner == null)
|
|
base.InternalCapture = value;
|
|
}
|
|
}
|
|
|
|
#endregion //internal methods
|
|
|
|
#region internal drawing methods
|
|
|
|
|
|
#endregion // internal drawing methods
|
|
|
|
#region inner classes and enumerations
|
|
|
|
// enumeration about what type of area on the calendar was hit
|
|
public enum HitArea {
|
|
Nowhere,
|
|
TitleBackground,
|
|
TitleMonth,
|
|
TitleYear,
|
|
NextMonthButton,
|
|
PrevMonthButton,
|
|
CalendarBackground,
|
|
Date,
|
|
NextMonthDate,
|
|
PrevMonthDate,
|
|
DayOfWeek,
|
|
WeekNumbers,
|
|
TodayLink
|
|
}
|
|
|
|
internal enum HitAreaExtra {
|
|
YearRectangle,
|
|
UpButton,
|
|
DownButton
|
|
}
|
|
|
|
// info regarding to a hit test on this calendar
|
|
public sealed class HitTestInfo {
|
|
|
|
private HitArea hit_area;
|
|
private Point point;
|
|
private DateTime time;
|
|
|
|
internal HitAreaExtra hit_area_extra;
|
|
internal DateTime hit_time;
|
|
|
|
// default constructor
|
|
internal HitTestInfo () {
|
|
hit_area = HitArea.Nowhere;
|
|
point = new Point (0, 0);
|
|
time = DateTime.Now;
|
|
}
|
|
|
|
// overload receives all properties
|
|
internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
|
|
this.hit_area = hit_area;
|
|
this.point = point;
|
|
this.time = time;
|
|
this.hit_time = time;
|
|
}
|
|
|
|
// overload receives all properties
|
|
internal HitTestInfo (HitArea hit_area, Point point, DateTime time, DateTime hit_time)
|
|
{
|
|
this.hit_area = hit_area;
|
|
this.point = point;
|
|
this.time = time;
|
|
this.hit_time = hit_time;
|
|
}
|
|
|
|
internal HitTestInfo (HitArea hit_area, Point point, DateTime time, HitAreaExtra hit_area_extra)
|
|
{
|
|
this.hit_area = hit_area;
|
|
this.hit_area_extra = hit_area_extra;
|
|
this.point = point;
|
|
this.time = time;
|
|
}
|
|
|
|
// the type of area that was hit
|
|
public HitArea HitArea {
|
|
get {
|
|
return hit_area;
|
|
}
|
|
}
|
|
|
|
// the point that is being test
|
|
public Point Point {
|
|
get {
|
|
return point;
|
|
}
|
|
}
|
|
|
|
// the date under the hit test point, only valid if HitArea is Date
|
|
public DateTime Time {
|
|
get {
|
|
return time;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion // inner classes
|
|
|
|
#region UIA Framework: Methods, Properties and Events
|
|
|
|
static object UIAMaxSelectionCountChangedEvent = new object ();
|
|
static object UIASelectionChangedEvent = new object ();
|
|
|
|
internal event EventHandler UIAMaxSelectionCountChanged {
|
|
add { Events.AddHandler (UIAMaxSelectionCountChangedEvent, value); }
|
|
remove { Events.RemoveHandler (UIAMaxSelectionCountChangedEvent, value); }
|
|
}
|
|
|
|
internal event EventHandler UIASelectionChanged {
|
|
add { Events.AddHandler (UIASelectionChangedEvent, value); }
|
|
remove { Events.RemoveHandler (UIASelectionChangedEvent, value); }
|
|
}
|
|
|
|
private void OnUIAMaxSelectionCountChanged ()
|
|
{
|
|
EventHandler eh = (EventHandler) Events [UIAMaxSelectionCountChangedEvent];
|
|
if (eh != null)
|
|
eh (this, EventArgs.Empty);
|
|
}
|
|
|
|
private void OnUIASelectionChanged ()
|
|
{
|
|
EventHandler eh = (EventHandler) Events [UIASelectionChangedEvent];
|
|
if (eh != null)
|
|
eh (this, EventArgs.Empty);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|