{ $Id$}
{
 *****************************************************************************
  This file is part of the Lazarus Component Library (LCL)

  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************

  Author: Vincent Snijders

  Abstract:
     Shows a non-modal calendar popup for a TDateEdit
}

unit CalendarPopup;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, Calendar, LCLProc, LCLType;
  
type
  TReturnDateEvent = procedure (Sender: TObject; const Date: TDateTime) of object;

  { TCalendarPopupForm }

  TCalendarPopupForm = class(TForm)
    Calendar: TCalendar;
    procedure CalendarDblClick(Sender: TObject);
    procedure CalendarKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormDeactivate(Sender: TObject);
  private
    FCaller: TControl;
    FClosed: boolean;
    FOnReturnDate: TReturnDateEvent;
    procedure Initialize(ADate: TDateTime;
      const DisplaySettings: TDisplaySettings;
      AMinDate, AMaxDate: TDateTime);
    procedure KeepInView(const PopupOrigin: TPoint);
    procedure ReturnDate;
  end;

procedure ShowCalendarPopup(const APosition: TPoint; ADate: TDateTime;
    const CalendarDisplaySettings: TDisplaySettings;
    AMinDate, AMaxDate: TDateTime;
    const OnReturnDate: TReturnDateEvent; const OnShowHide: TNotifyEvent = nil;
    ACaller: TControl = nil);

procedure ShowCalendarPopup(const APosition: TPoint; ADate: TDateTime;
    const CalendarDisplaySettings: TDisplaySettings;
    const OnReturnDate: TReturnDateEvent; const OnShowHide: TNotifyEvent = nil;
    ACaller: TControl = nil);

implementation

{$R *.lfm}

procedure ShowCalendarPopup(const APosition: TPoint; ADate: TDateTime;
  const CalendarDisplaySettings: TDisplaySettings;
  AMinDate, AMaxDate: TDateTime;
  const OnReturnDate: TReturnDateEvent; const OnShowHide: TNotifyEvent;
  ACaller: TControl);
var
  PopupForm: TCalendarPopupForm;
begin
  PopupForm := TCalendarPopupForm.Create(nil);
  PopupForm.FCaller := ACaller;
  PopupForm.Initialize(ADate, CalendarDisplaySettings, AMinDate, AMaxDate);
  PopupForm.FOnReturnDate := OnReturnDate;
  PopupForm.OnShow := OnShowHide;
  PopupForm.OnHide := OnShowHide;
  PopupForm.Show;
  PopupForm.KeepInView(APosition);   // must be after Show for PopupForm.AutoSize to be in effect.
end;

procedure ShowCalendarPopup(const APosition: TPoint; ADate: TDateTime;
    const CalendarDisplaySettings: TDisplaySettings;
    const OnReturnDate: TReturnDateEvent; const OnShowHide: TNotifyEvent = nil;
    ACaller: TControl = nil);
begin
  ShowCalendarPopup(APosition, ADate, CalendarDisplaySettings, 0.0, 0.0, OnReturnDate, OnShowHide, Acaller)
end;

{ TCalendarPopupForm }

procedure TCalendarPopupForm.FormCreate(Sender: TObject);
begin
  FClosed := false;
  Application.AddOnDeactivateHandler(@FormDeactivate);
end;

procedure TCalendarPopupForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  //DebugLn(['TCalendarPopupForm.FormClose ']);
  FClosed := true;
  Application.RemoveOnDeactivateHandler(@FormDeactivate);
  CloseAction := caFree;
end;

procedure TCalendarPopupForm.CalendarDblClick(Sender: TObject);
var
  P: TPoint;
  htRes: TCalendarPart;
begin
  P := Calendar.ScreenToClient(Mouse.CursorPos);
  htRes := Calendar.HitTest(P);
  if {(htRes = cpNoWhere) or }((htRes = cpDate) and (Calendar.GetCalendarView = cvMonth)) then
    ReturnDate;
end;

procedure TCalendarPopupForm.CalendarKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  Handled: Boolean;
begin
  if Shift=[] then
  begin
    Handled := true;
    case Key of
    VK_ESCAPE:
      Close;
    VK_RETURN, VK_SPACE:
      if (Calendar.GetCalendarView = cvMonth) then
        ReturnDate
      else
        Handled := False;
    else
      Handled := false;
    end;
    if Handled then
      Key := 0;
  end;
end;

procedure TCalendarPopupForm.FormDeactivate(Sender: TObject);
begin
  //DebugLn(['TCalendarPopupForm.FormDeactivate ',DbgSName(GetCaptureControl)]);
  //Immediately hide the form, otherwise it stays visible while e.g. user is draging
  //another form (Issue #0020647)
  Hide;
  if (not FClosed) then
    Close;
end;

procedure TCalendarPopupForm.Initialize(ADate: TDateTime;
  const DisplaySettings: TDisplaySettings; AMinDate, AMaxDate: TDateTime);
begin
  Calendar.DateTime := ADate;
  Calendar.DisplaySettings:=DisplaySettings;
  Calendar.MinDate := AMinDate;
  Calendar.MaxDate := AMaxDate;
end;

procedure TCalendarPopupForm.KeepInView(const PopupOrigin: TPoint);
var
  ABounds: TRect;
  P: TPoint;
begin
  ABounds := Screen.MonitorFromPoint(PopupOrigin).WorkAreaRect; // take care of taskbar
  if PopupOrigin.X + Width > ABounds.Right then
    Left := ABounds.Right - Width
  else if PopupOrigin.X < ABounds.Left then
    Left := ABounds.Left
  else
    Left := PopupOrigin.X;
  if PopupOrigin.Y + Height > ABounds.Bottom then
  begin
    if Assigned(FCaller) then
      Top := PopupOrigin.Y - FCaller.Height - Height
    else
      Top := ABounds.Bottom - Height;
  end else if PopupOrigin.Y < ABounds.Top then
    Top := ABounds.Top
  else
    Top := PopupOrigin.Y;
  if Left < ABounds.Left then Left := 0;
  if Top < ABounds.Top then Top := 0;
end;

procedure TCalendarPopupForm.ReturnDate;
begin
  if Assigned(FOnReturnDate) then
    FOnReturnDate(Self, Calendar.DateTime);
  if not FClosed then
    Close;
end;

end.
