{*********************************************************} {* VPDATEEDIT.PAS 1.03 *} {*********************************************************} {* ***** BEGIN LICENSE BLOCK ***** *} {* Version: MPL 1.1 *} {* *} {* The contents of this file are subject to the Mozilla Public License *} {* Version 1.1 (the "License"); you may not use this file except in *} {* compliance with the License. You may obtain a copy of the License at *} {* http://www.mozilla.org/MPL/ *} {* *} {* Software distributed under the License is distributed on an "AS IS" basis, *} {* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License *} {* for the specific language governing rights and limitations under the *} {* License. *} {* *} {* The Original Code is TurboPower Visual PlanIt *} {* *} {* The Initial Developer of the Original Code is TurboPower Software *} {* *} {* Portions created by TurboPower Software Inc. are Copyright (C) 2002 *} {* TurboPower Software Inc. All Rights Reserved. *} {* *} {* Contributor(s): *} {* *} {* ***** END LICENSE BLOCK ***** *} {$I Vp.INC} unit VpDateEdit; {-date edit field with popup calendar} interface uses {$IFDEF LCL} LCLProc, LCLType, LCLIntf, {$ELSE} Windows, {$ENDIF} Buttons, Classes, Controls, Forms, Graphics, Menus, StdCtrls, SysUtils, VpCalendar, VpConst, VpEdPop, VpMisc; type TVpDateOrder = (doMDY, doDMY, doYMD); TVpRequiredField = (rfYear, rfMonth, rfDay); TVpRequiredFields = set of TVpRequiredField; TVpGetDateEvent = procedure(Sender : TObject; var Value : string) of object; TVpCustomDateEdit = class(TVpEdPopup) protected {private} {property variables} FAllowIncDec : Boolean; FDate : TDateTime; FEpoch : Integer; FForceCentury : Boolean; FPopupCalColors : TVpCalColors; FPopupCalFont : TFont; FPopupCalHeight : Integer; FPopupCalWidth : Integer; FRequiredFields : TVpRequiredFields; FTodayString : string; FWeekStarts : TVpDayType; {the day that begins the week} {event variables} FOnGetDate : TVpGetDateEvent; FOnSetDate : TNotifyEvent; {internal variables} Calendar : TVpCalendar; DateOrder : TVpDateOrder; GettingDate : Boolean; HoldCursor : TCursor; WasAutoScroll : Boolean; {property methods} function GetDate : TDateTime; function GetReadOnly : Boolean; procedure SetForceCentury(Value : Boolean); procedure SetPopupCalFont(Value : TFont); procedure SetReadOnly(Value : Boolean); {internal methods} procedure PopupDateChange(Sender : TObject; Date : TDateTime); procedure PopupKeyDown(Sender : TObject; var Key : Word; Shift : TShiftState); procedure PopupKeyPress(Sender : TObject; var Key : Char); procedure PopupMouseDown(Sender : TObject; Button : TMouseButton; Shift : TShiftState; X, Y : Integer); protected procedure DoExit; override; procedure KeyDown(var Key : Word; Shift : TShiftState); override; procedure KeyPress(var Key : Char); override; procedure PopupClose(Sender : TObject); override; procedure SetDate(Value : TDateTime); procedure SetDateText(Value : string); dynamic; {protected properties} property AllowIncDec : Boolean read FAllowIncDec write FAllowIncDec default True; property Epoch : Integer read FEpoch write FEpoch; property ForceCentury : Boolean read FForceCentury write SetForceCentury default False; property PopupCalColors : TVpCalColors read FPopupCalColors write FPopupCalColors; property PopupCalFont : TFont read FPopupCalFont write SetPopupCalFont; property PopupCalHeight : Integer read FPopupCalHeight write FPopupCalHeight default calDefHeight; property PopupCalWidth : Integer read FPopupCalWidth write FPopupCalWidth default calDefWidth; property ReadOnly : Boolean read GetReadOnly write SetReadOnly; property RequiredFields : TVpRequiredFields read FRequiredFields write FRequiredFields; property TodayString : string read FTodayString write FTodayString; property WeekStarts : TVpDayType read FWeekStarts write FWeekStarts default calDefWeekStarts; {protected events} property OnGetDate : TVpGetDateEvent read FOnGetDate write FOnGetDate; property OnSetDate : TNotifyEvent read FOnSetDate write FOnSetDate; public constructor Create(AOwner : TComponent); override; destructor Destroy; override; procedure PopupOpen; override; function FormatDate(Value : TDateTime) : string; dynamic; {public properties} property Date : TDateTime read GetDate write SetDate; end; TVpDateEdit = class(TVpCustomDateEdit) published {properties} {$IFDEF VERSION4} property Anchors; property Constraints; property DragKind; {$ENDIF} property AllowIncDec; property AutoSelect; property AutoSize; property BorderStyle; property CharCase; property Color; property Cursor; property DragCursor; property DragMode; property Enabled; property Epoch; property Font; property ForceCentury; {$IFNDEF LCL} property HideSelection; {$ENDIF} property ParentColor; property ParentFont; property ParentShowHint; property PopupCalColors; property PopupCalFont; property PopupCalHeight; property PopupCalWidth; property PopupMenu; property ReadOnly; property RequiredFields; property ShowHint; property ShowButton; property TabOrder; property TabStop; property TodayString; property Version; property Visible; property WeekStarts; {events} property OnChange; property OnClick; property OnDblClick; property OnDragDrop; property OnDragOver; property OnEndDrag; property OnEnter; property OnExit; property OnGetDate; property OnKeyDown; property OnKeyPress; property OnKeyUp; property OnMouseDown; property OnMouseMove; property OnMouseUp; property OnSetDate; property OnStartDrag; end; implementation uses VpSR, VpException; {*** TVpCustomDateEdit ***} constructor TVpCustomDateEdit.Create(AOwner : TComponent); var C : array[0..1] of Char; begin inherited Create(AOwner); ControlStyle := ControlStyle - [csSetCaption]; FAllowIncDec := True; FEpoch := DefaultEpoch; FForceCentury := False; FRequiredFields := [rfMonth, rfDay]; FTodayString := DateSeparator; FPopupCalHeight := calDefHeight; FPopupCalWidth := calDefWidth; FPopupCalFont := TFont.Create; FPopupCalFont.Assign(Font); {get the date order from windows} C[0] := '0'; {default} //TODO: {$IFDEF DELPHI} GetProfileString('intl', 'iDate', '0', C, 2); {load button glyph} FButton.Glyph.Handle := LoadBaseBitmap('VPBTNCAL'); {$ELSE} DateOrder := TVpDateOrder(Ord(C[0])-Ord('0')); {load button glyph} FButton.Glyph.LoadFromResourceName(HINSTANCE,'VPBTNCAL'); {$ENDIF} {create color class} FPopupCalColors := TVpCalColors.Create; {assign default color scheme} FPopupCalColors.FCalColors := CalScheme[cscalWindows]; FPopupCalColors.FColorScheme := cscalWindows; GettingDate := False; end; {=====} destructor TVpCustomDateEdit.Destroy; begin FPopupCalColors.Free; FPopupCalColors := nil; FPopupCalFont.Free; FPopupCalFont := nil; inherited Destroy; end; {=====} procedure TVpCustomDateEdit.DoExit; begin try SetDateText(Text); except SetFocus; raise; end; if not PopupActive then inherited DoExit; end; {=====} function TVpCustomDateEdit.GetDate : TDateTime; begin GettingDate := True; try SetDateText(Text); finally GettingDate := False; end; Result := FDate; end; {=====} function TVpCustomDateEdit.GetReadOnly : Boolean; begin Result := inherited ReadOnly; end; {=====} procedure TVpCustomDateEdit.KeyDown(var Key : Word; Shift : TShiftState); begin inherited KeyDown(Key, Shift); if (Key = VK_DOWN) and (ssAlt in Shift) then PopupOpen; end; {=====} procedure TVpCustomDateEdit.KeyPress(var Key : Char); var D : Word; M : Word; Y : Word; begin inherited KeyPress(Key); if FAllowIncDec and (Key in ['+', '-']) then begin DoExit; {accept current date} if FDate = 0 then DecodeDate(SysUtils.Date, Y, M, D) else DecodeDate(FDate, Y, M, D); if Key = '+' then begin Inc(D); if D > DaysInMonth(Y, M) then begin D := 1; Inc(M); if M > 12 then begin Inc(Y); M := 1; end; end; end else {'-'} begin Dec(D); if D < 1 then begin Dec(M); if M < 1 then begin M := 12; Dec(Y); end; D := DaysInMonth(Y, M); end; end; SetDate(EncodeDate(Y, M, D)); Modified := True; Key := #0; {clear} end; end; {=====} function TVpCustomDateEdit.FormatDate(Value : TDateTime) : string; var S : string; begin S := ShortDateFormat; if FForceCentury then if Pos('yyyy', S) = 0 then Insert('yy', S, Pos('yy', S)); Result := FormatDateTime(S, Value) end; {=====} procedure TVpCustomDateEdit.PopupClose(Sender : TObject); begin inherited PopupClose(Sender); if GetCapture = Calendar.Handle then ReleaseCapture; SetFocus; Calendar.Hide; {hide the Calendar} if (Calendar.Parent <> nil) then if (Calendar.Parent is TForm) then TForm(Calendar.Parent).AutoScroll := WasAutoScroll else if (Calendar.Parent is TScrollBox) then TScrollBox(Calendar.Parent).AutoScroll := WasAutoScroll; Cursor := HoldCursor; {change parentage so that we control the window handle destruction} Calendar.Parent := Self; end; {=====} procedure TVpCustomDateEdit.PopupMouseDown(Sender : TObject; Button : TMouseButton; Shift : TShiftState; X, Y : Integer); var P : TPoint; I : Integer; begin Unused(Button, Shift); P := Point(X,Y); if not PtInRect(Calendar.ClientRect, P) then PopUpClose(Sender); {convert to our coordinate system} P := ScreenToClient(Calendar.ClientToScreen(P)); if PtInRect(ClientRect, P) then begin I := SelStart; SetFocus; SelStart := I; SelLength := 0; end; end; {=====} procedure TVpCustomDateEdit.PopupOpen; var P: TPoint; {$IFNDEF LCL} MeasureFrom: TPoint; {$ENDIF} begin inherited PopupOpen; DoExit; {force update of date} if not Assigned(Calendar) then begin Calendar := TVpCalendar.CreateEx (Self, True); Calendar.OnChange := PopupDateChange; Calendar.OnExit := PopupClose; Calendar.OnKeyDown := PopupKeyDown; Calendar.OnKeyPress := PopupKeyPress; Calendar.OnMouseDown := PopupMouseDown; Calendar.Visible := False; {to avoid flash at 0,0} Calendar.BorderStyle := bsSingle; Calendar.Height := FPopupCalHeight; Calendar.Width := FPopupCalWidth; Calendar.WeekStarts := FWeekStarts; Calendar.Font.Assign(FPopupCalFont); end; if (Parent.Parent <> nil) then Calendar.Parent := Parent.Parent else if Parent <> nil then Calendar.Parent := Parent else Calendar.Parent := GetParentForm(Self); if (Calendar.Parent <> nil) then if (Calendar.Parent is TForm) then begin WasAutoScroll := TForm(Calendar.Parent).AutoScroll; TForm(Calendar.Parent).AutoScroll := False; end else if (Calendar.Parent is TScrollBox) then begin WasAutoScroll := TScrollBox(Calendar.Parent).AutoScroll; TScrollBox(Calendar.Parent).AutoScroll := False; end; {set colors} Calendar.Colors.Assign(FPopupCalColors); {determine the proper position} P := Point (Left, Top + Height + 2); {$IFNDEF LCL} MeasureFrom := Point(0, 0); {$ENDIF} if Assigned (Parent) and (not (Parent is TForm)) then begin P.x := P.x + Parent.Left; P.y := P.y + Parent.Top; end; //TODO: {$IFNDEF LCL} MoveWindow (Calendar.Handle, MeasureFrom.x + P.X, MeasureFrom.y + P.Y, Calendar.Width, Calendar.Height, False); {$ENDIF} if Text = '' then Calendar.Date := Now else Calendar.Date := FDate; HoldCursor := Cursor; Cursor := crArrow; Calendar.Show; Calendar.SetFocus; SetCapture(Calendar.Handle); end; {=====} procedure TVpCustomDateEdit.PopupDateChange(Sender : TObject; Date : TDateTime); begin Unused(Date); {get the current value} SetDate(Calendar.Date); Modified := True; if Calendar.Browsing then Exit; {hide the Calendar} PopupClose(Sender); SetFocus; SelStart := Length(Text); SelLength := 0; end; {=====} procedure TVpCustomDateEdit.PopupKeyDown(Sender : TObject; var Key : Word; Shift : TShiftState); var X : Integer; begin case Key of VK_UP : if Shift = [ssAlt] then begin PopupClose(Sender); X := SelStart; SetFocus; SelStart := X; SelLength := 0; end; end; end; {=====} procedure TVpCustomDateEdit.PopupKeyPress(Sender : TObject; var Key : Char); var X : Integer; begin case Key of #27 : begin PopupClose(Sender); X := SelStart; SetFocus; SelStart := X; SelLength := 0; end; end; end; {=====} procedure TVpCustomDateEdit.SetDate(Value : TDateTime); begin FDate := Value; Modified := True; if FDate = 0 then Text := '' else Text := FormatDate(FDate); if Assigned(FOnSetDate) then FOnSetDate(Self); end; {=====} procedure TVpCustomDateEdit.SetDateText(Value : string); var Field : Integer; I1 : Integer; I2 : Integer; Error : Integer; ThisYear : Word; ThisMonth : Word; ThisDay : Word; Year : Word; Month : Word; Day : Word; EpochYear : Integer; EpochCent : Integer; StringList : TStringList; FieldOrder : string[3]; S : string; const ErrorConvertingMonthNumber = 1; ErrorConvertingMonthName = 2; ErrorConvertingYear = 3; ErrorConvertingDay = 4; MonthIsRequired = 5; DayIsRequired = 6; YearIsRequired = 7; begin if Assigned(FOnGetDate) then FOnGetDate(Self, Value); if (Value = '') and (RequiredFields <> []) then begin FDate := 0; if not GettingDate then Text := ''; Exit; end; if AnsiCompareText(Value, TodayString) = 0 then begin FDate := SysUtils.Date; if not GettingDate then begin Text := FormatDate(FDate); Modified := True; end; end else begin DecodeDate(SysUtils.Date, ThisYear, ThisMonth, ThisDay); Value := UpperCase(Value); StringList := TStringList.Create; try {parse the string into subfields using a string list to hold the parts} I1 := 1; while (I1 <= Length(Value)) and not (Value[I1] in ['0'..'9', 'A'..'Z']) do Inc(I1); while I1 <= Length(Value) do begin I2 := I1; while (I2 <= Length(Value)) and (Value[I2] in ['0'..'9', 'A'..'Z']) do Inc(I2); StringList.Add(Copy(Value, I1, I2-I1)); while (I2 <= Length(Value)) and not (Value[I2] in ['0'..'9', 'A'..'Z']) do Inc(I2); I1 := I2; end; case DateOrder of doMDY : FieldOrder := 'MDY'; doDMY : FieldOrder := 'DMY'; doYMD : FieldOrder := 'YMD'; end; Year := 0; Month := 0; Day := 0; Error := 0; for Field := 1 to Length(FieldOrder) do begin if StringList.Count > 0 then S := StringList[0] else S := ''; case FieldOrder[Field] of 'M' : begin {numeric month} if (S = '') or (S[1] in ['0'..'9']) then begin try if S = '' then Month := 0 else Month := StrToInt(S); except Month := 0; {error converting month number} Error := ErrorConvertingMonthNumber; end; if not (Month in [1..12]) then Month := 0; end else begin {one or more letters in month} Month := 0; I1 := 1; S := Copy(S, 1, 3); {error converting month name} Error := ErrorConvertingMonthName; repeat if S = UpperCase(Copy(ShortMonthNames[I1], 1, Length(S))) then begin Month := I1; I1 := 13; Error := 0; end else Inc(I1); until I1 = 13; end; if Month = 0 then begin if rfMonth in FRequiredFields then {month required} Error := MonthIsRequired else Month := ThisMonth; end else if StringList.Count > 0 then StringList.Delete(0); if Error > 0 then Break; end; 'Y' : begin try if S = '' then Year := 0 else Year := StrToInt(S); except Year := 0; {error converting year} Error := ErrorConvertingYear; end; if (FEpoch = 0) and (Year < 100) and (S <> '') then {default to current century if Epoch is zero} Year := Year + (ThisYear div 100 * 100) else if (FEpoch > 0) and (Year < 100) and (S <> '') then begin {use epoch} EpochYear := FEpoch mod 100; EpochCent := (FEpoch div 100) * 100; if (Year < EpochYear) then Inc(Year,EpochCent+100) else Inc(Year,EpochCent); end; if Year = 0 then begin if rfYear in FRequiredFields then {year is required} Error := YearIsRequired else Year := ThisYear; end else if StringList.Count > 0 then StringList.Delete(0); if Error > 0 then Break; end; 'D' : begin try if S = '' then Day := 0 else Day := StrToInt(S); except Day := 0; {error converting day} Error := ErrorConvertingDay; end; if not (Day in [1..31]) then Day := 0; if Day = 0 then begin if rfDay in FRequiredFields then {day is required} Error := DayIsRequired else Day := ThisDay; end else if StringList.Count > 0 then StringList.Delete(0); if Error > 0 then Break; end; end; end; case Error of ErrorConvertingDay : if S = '' then raise EVpDateEditError.Create(RSInvalidDay + ' "' + Value + '"') else raise EVpDateEditError.Create(RSInvalidDay + ' "' + S + '"'); ErrorConvertingMonthNumber : if S = '' then raise EVpDateEditError.Create(RSInvalidMonth + ' "' + Value + '"') else raise EVpDateEditError.Create(RSInvalidMonth + ' "' + S + '"'); ErrorConvertingMonthName : if S = '' then raise EVpDateEditError.Create(RSInvalidMonthName + ' "' + Value + '"') else raise EVpDateEditError.Create(RSInvalidMonthName + ' "' + S + '"'); ErrorConvertingYear : if S = '' then raise EVpDateEditError.Create(RSInvalidYear + ' "' + Value + '"') else raise EVpDateEditError.Create(RSInvalidYear + ' "' + S + '"'); DayIsRequired : raise EVpDateEditError.Create(RSDayIsRequired); MonthIsRequired : raise EVpDateEditError.Create(RSMonthIsRequired); YearIsRequired : raise EVpDateEditError.Create(RSYearIsRequired); end; try FDate := EncodeDate(Year, Month, Day); if not GettingDate then Text := FormatDate(FDate); except raise EVpDateEditError.Create(RSInvalidDate + ' "' + Value + '"'); end; finally StringList.Free; end; end; end; {=====} procedure TVpCustomDateEdit.SetForceCentury(Value : Boolean); begin if Value <> FForceCentury then begin FForceCentury := Value; if Assigned(Calendar) then SetDate(Calendar.Date); end; end; {=====} procedure TVpCustomDateEdit.SetPopupCalFont(Value : TFont); begin if Assigned(Value) then FPopupCalFont.Assign(Value); end; {=====} procedure TVpCustomDateEdit.SetReadOnly(Value : Boolean); begin inherited ReadOnly := Value; FButton.Enabled := not ReadOnly; end; {=====} end.