{
TDBDateTimePicker control for Lazarus
- - - - - - - - - - - - - - - - - - - -
Author: Zoran Vučenović, January and February 2010
        Зоран Вученовић, јануар и фебруар 2010.

This unit is part of DateTimeCtrls package for Lazarus.
TDBDateTimePicker is data-aware version of TDateTimePicker control.

-----------------------------------------------------------
LICENCE
- - - -
   Modified LGPL -- see the file COPYING.modifiedLGPL.

-----------------------------------------------------------
NO WARRANTY
- - - - - -
   There is no warranty whatsoever.

-----------------------------------------------------------
BEST REGARDS TO LAZARUS COMMUNITY!
- - - - - - - - - - - - - - - - - -
   I do hope this control will be useful.
}
unit DBDateTimePicker;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, db,
  // LCL
  DBCtrls, LMessages,
  // DateTimeCtrls
  DateTimePicker;

type

  { TDBDateTimePicker }

  TDBDateTimePicker = class(TCustomDateTimePicker)
  private
    { Private declarations }
    FDataLink: TFieldDataLink;
    FReadOnly: Boolean;
    FDataChangeCount: Integer;
    FChangingCount: Integer;
    function GetDataField: string;
    function GetDataSource: TDataSource;
    procedure SetDataField(const AValue: string);
    procedure SetDataSource(const AValue: TDataSource);
    procedure DataChange(Sender: TObject);
    procedure SetReadOnly(const AValue: Boolean);
    procedure UpdateData(Sender: TObject);
    procedure ActiveChange(Sender: TObject);
    function GetField: TField;
    procedure CheckField;
    procedure CMGetDataLink(var Message: TLMessage); message CM_GETDATALINK;
  protected
    { Protected declarations }
    procedure Change; override;
    procedure ConfirmChanges; override;
    procedure UndoChanges; override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Field: TField read GetField;
    property CalendarWrapperClass;
    property DroppedDown;
  published
    { Published declarations }
    property DataField: string read GetDataField write SetDataField;
    property DataSource: TDataSource read GetDataSource write SetDataSource;
    property ReadOnly: Boolean read FReadOnly write SetReadOnly;

    property ArrowShape;
    property ShowCheckBox;
    property Checked;
    property CenturyFrom;
    property DateDisplayOrder;
    property MaxDate;
    property MinDate;
    property AutoSize;
    property Font;
    property ParentFont;
    property TabOrder;
    property TabStop;
    property BorderStyle;
    property BorderSpacing;
    property Enabled;
    property Color;
    property ParentColor;
    property DateSeparator;
    property TrailingSeparator;
    property TextForNullDate;
    property LeadingZeros;
    property ShowHint;
    property ParentShowHint;
    property Align;
    property Alignment;
    property Anchors;
    property Constraints;
    property Cursor;
    property PopupMenu;
    property Visible;
    property NullInputAllowed;
    property Kind;
    property TimeSeparator;
    property DecimalSeparator;
    property TimeFormat;
    property TimeDisplay;
    property DateMode;
    property UseDefaultSeparators;
    property Cascade;
    property AutoButtonSize;
    property AutoAdvance;
    property HideDateTimeParts;
    property BiDiMode;
    property ParentBiDiMode;
    property MonthNames;
    property ShowMonthNames;
    property CalAlignment;
    property Options;
  //events:
    property OnChange;
    property OnCheckBoxChange;
    property OnDropDown;
    property OnCloseUp;
    property OnChangeBounds;
    property OnClick;
    property OnContextPopup;
    property OnDblClick;
    property OnEditingDone;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheel;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
    property OnResize;
    property OnShowHint;
    property OnUTF8KeyPress;
  end;

implementation

{ TDBDateTimePicker }

function TDBDateTimePicker.GetDataField: string;
begin
  Result := FDataLink.FieldName;
end;

function TDBDateTimePicker.GetDataSource: TDataSource;
begin
  Result := FDataLink.DataSource;
end;

procedure TDBDateTimePicker.SetDataField(const AValue: string);
begin
  FDataLink.FieldName := AValue;
  CheckField;
end;

procedure TDBDateTimePicker.SetDataSource(const AValue: TDataSource);
begin
  ChangeDataSource(Self, FDataLink, AValue);
  CheckField;
end;

procedure TDBDateTimePicker.DataChange(Sender: TObject);
begin
  if (FChangingCount = 0) then begin
    Inc(FDataChangeCount);
    try
      if Assigned(FDataLink.Field) and not FDataLink.Field.IsNull then begin
        // Using the SetTheDateJumpMinMax procedure, instead of property
        SetDateTimeJumpMinMax(FDataLink.Field.AsDateTime); // assignment allows
            // this control to display dates from database whose value falls
            // outside of MinDate and MaxDate interval.
            // Note that user still cannot enter such values in the control.
      end else
        DateTime := NullDate;

    finally
      Dec(FDataChangeCount);
    end;
  end;
end;

procedure TDBDateTimePicker.SetReadOnly(const AValue: Boolean);
begin
  if FReadOnly <> AValue then begin
    FReadOnly := AValue;
    CheckField;
  end;
end;

procedure TDBDateTimePicker.UpdateData(Sender: TObject);
begin
  if Assigned(FDataLink.Field) then begin
    if DateIsNull then
      FDataLink.Field.AsVariant := Null
    else
      FDataLink.Field.AsDateTime := DateTime;
  end;
end;

procedure TDBDateTimePicker.ActiveChange(Sender: TObject);
begin
  CheckField;
end;

function TDBDateTimePicker.GetField: TField;
begin
  Result := FDataLink.Field;
end;

procedure TDBDateTimePicker.CheckField;
begin
  if (FDataLink.Active) and Assigned(FDataLink.Field) then
    inherited ReadOnly := FReadOnly or (not FDataLink.CanModify)
  else begin
    inherited ReadOnly := True;
    DateTime := NullDate;
  end;
end;

procedure TDBDateTimePicker.CMGetDataLink(var Message: TLMessage);
begin
  Message.Result := PtrUInt(FDataLink);
end;

procedure TDBDateTimePicker.Change;
begin
  if (FDataChangeCount <= 0) and Assigned(FDataLink) then begin
    Inc(FChangingCount);
    try
      if FDataLink.Edit then begin
        FDataLink.Modified;
        inherited Change; // calls OnChange event handler
      end else
        FDataLink.Reset; // reverts user changes
    finally
      Dec(FChangingCount);
    end;
  end;
end;

procedure TDBDateTimePicker.ConfirmChanges;
begin
  inherited ConfirmChanges;

  if Assigned(FDataLink) then
    try
      FDataLink.UpdateRecord;
    except
      SetFocus;
      raise;
    end;

end;

procedure TDBDateTimePicker.UndoChanges;
begin
  FDataLink.Reset;

  inherited UndoChanges;
end;

constructor TDBDateTimePicker.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FDataChangeCount := 0;
  FChangingCount := 0;
  FDataLink := TFieldDataLink.Create;
  FDataLink.Control := Self;
  DateTime := NullDate;
  FDataLink.OnActiveChange := @ActiveChange;
  FDataLink.OnDataChange := @DataChange;
  FDataLink.OnUpdateData := @UpdateData;

  CheckField;
end;

destructor TDBDateTimePicker.Destroy;
begin
  FDataLink.OnUpdateData := nil;
  FDataLink.OnDataChange := nil;
  FDataLink.OnActiveChange := nil;
  FreeAndNil(FDataLink);

  inherited Destroy;
end;

end.
