{-----------------------------------------------------------------------------
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/MPL-1.1.html

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

The Original Code is: JvTFAlarm.PAS, released on 2003-08-01.

The Initial Developer of the Original Code is Unlimited Intelligence Limited.
Portions created by Unlimited Intelligence Limited are Copyright (C) 1999-2002 Unlimited Intelligence Limited.
All Rights Reserved.

Contributor(s):
Mike Kolter (original code)

You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.delphi-jedi.org

Known Issues:
-----------------------------------------------------------------------------}
// $Id$

unit JvTFAlarm;

{$I jvcl.inc}

interface

uses
  {$IFDEF UNITVERSIONING}
  JclUnitVersioning,
  {$ENDIF UNITVERSIONING}
  SysUtils, Classes, Controls, ExtCtrls,
  {$IFDEF BCB}
  JvTypes,
  {$ENDIF BCB}
  JvTFManager;

type
  TJvTFAlarm = class;

  TJvTFAlarmInfo = class(TObject)
  private
    FAppt: TJvTFAppt;
    FSnoozeMins: Integer;
    FDismiss: Boolean;
    FNextAlarmTime: TTime;
  protected
    property NextAlarmTime: TTime read FNextAlarmTime write FNextAlarmTime;
  public
    constructor Create(AAppt: TJvTFAppt); virtual;
    property Appt: TJvTFAppt read FAppt;
    property SnoozeMins: Integer read FSnoozeMins write FSnoozeMins;
    property Dismiss: Boolean read FDismiss write FDismiss;
  end;

  TJvTFAlarmList = class(TStringList)
  private
    FOwner: TJvTFAlarm;
  public
    procedure Clear; override;
    function GetAlarmForAppt(AAppt: TJvTFAppt): TJvTFAlarmInfo;
    function GetAlarmForApptID(const ID: string): TJvTFAlarmInfo;
    function IndexOfAppt(AAppt: TJvTFAppt): Integer;
    procedure AddAppt(AAppt: TJvTFAppt);
    procedure DeleteAppt(AAppt: TJvTFAppt);
    property Owner: TJvTFAlarm read FOwner write FOwner;
  end;

  TJvTFAlarmEvent = procedure(Sender: TObject; AAppt: TJvTFAppt;
    var SnoozeMins: Integer; var Dismiss: Boolean) of object;

  {$IFDEF RTL230_UP}
  [ComponentPlatformsAttribute(pidWin32 or pidWin64{$IFDEF RTL360_UP} or pidWin64x{$ENDIF RTL360_UP})]
  {$ENDIF RTL230_UP}
  TJvTFAlarm = class(TJvTFComponent)
  private
    FResources: TStringList;
    FTimer: TTimer;
    FCurrentDate: TDate;
    FAlarmList: TJvTFAlarmList;
    FOnAlarm: TJvTFAlarmEvent;
    FDefaultSnoozeMins: Integer;
    function GetResources: TStrings;
    procedure SetResources(Value: TStrings);
    function GetTimerInterval: Integer;
    procedure SetTimerInterval(Value: Integer);
    function GetEnabled: Boolean;
    procedure SetEnabled(Value: Boolean);
    procedure InternalTimer(Sender: TObject);
  protected
    procedure DestroyApptNotification(AAppt: TJvTFAppt); override;
    procedure ConnectSchedules; virtual;
    procedure DisconnectSchedules; virtual;
    procedure TimerCheck; virtual;
    procedure AlarmCheck; virtual;
    procedure Loaded; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Resources: TStrings read GetResources write SetResources;
    property TimerInterval: Integer read GetTimerInterval write SetTimerInterval default 30000;
    property Enabled: Boolean read GetEnabled write SetEnabled default True;
    property DefaultSnoozeMins: Integer read FDefaultSnoozeMins write FDefaultSnoozeMins default 5;
    property OnAlarm: TJvTFAlarmEvent read FOnAlarm write FOnAlarm;
  end;

{$IFDEF UNITVERSIONING}
const
  UnitVersioning: TUnitVersionInfo = (
    RCSfile: '$URL$';
    Revision: '$Revision$';
    Date: '$Date$';
    LogPath: 'JVCL\run'
  );
{$ENDIF UNITVERSIONING}

implementation

uses
  JvTFUtils;

//=== { TJvTFAlarm } =========================================================

constructor TJvTFAlarm.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FDefaultSnoozeMins := 5;
  FCurrentDate := Date;
  FResources := TStringList.Create;
  FTimer := TTimer.Create(Self);
  FTimer.Interval := 30000;
  FTimer.Enabled := True;
  FTimer.OnTimer := InternalTimer;
  FAlarmList := TJvTFAlarmList.Create;
  FAlarmList.Owner := Self;
end;

destructor TJvTFAlarm.Destroy;
begin
  DisconnectSchedules;
  FTimer.Free;
  FResources.Free;
  FAlarmList.Create;
  FAlarmList.Free;
  inherited Destroy;
end;

procedure TJvTFAlarm.Loaded;
begin
  inherited Loaded;
  ConnectSchedules;
end;

procedure TJvTFAlarm.AlarmCheck;
var
  I, J, SnoozeMins: Integer;
  Dismiss: Boolean;
  Sched: TJvTFSched;
  Appt: TJvTFAppt;
  AlarmInfo: TJvTFAlarmInfo;
  AlarmTime: TTime;
begin
  // 1. Roll through all schedules and add an alarm for each appt with a start
  //    time that is less than the current time.  (Duplicate appts will be ignored.)
  // 2. Roll through the alarm list and fire an OnAlarm event when appropriate.

  // 1.
  for I := 0 to ScheduleCount - 1 do
  begin
    Sched := Schedules[I];
    for J := 0 to Sched.ApptCount - 1 do
    begin
      Appt := Sched.Appts[J];
      AlarmTime := Appt.StartTime - Appt.AlarmAdvance * ONE_MINUTE;
      if (AlarmTime < Frac(Time)) and Appt.AlarmEnabled then
        FAlarmList.AddAppt(Appt);
    end;
  end;

  // 2.
  for I := 0 to FAlarmList.Count - 1 do
  begin
    AlarmInfo := TJvTFAlarmInfo(FAlarmList.Objects[I]);
    if not AlarmInfo.Dismiss and (AlarmInfo.NextAlarmTime < Frac(Time)) then
    begin
      SnoozeMins := AlarmInfo.SnoozeMins;
      Dismiss := False;
      if Assigned(FOnAlarm) then
      begin
        FOnAlarm(Self, AlarmInfo.Appt, SnoozeMins, Dismiss);
        AlarmInfo.SnoozeMins := SnoozeMins;
        AlarmInfo.Dismiss := Dismiss;
      end;
      AlarmInfo.NextAlarmTime := Time + SnoozeMins * ONE_MINUTE;
    end;
  end;
end;

procedure TJvTFAlarm.ConnectSchedules;
var
  I: Integer;
  CurrentSchedules: TStringList;
  Schedule: TJvTFSched;
begin
  CurrentSchedules := TStringList.Create;
  try
    FTimer.Enabled := False;
    // request all appropriate schedules.  Store in temporary list so that
    // we can release all schedules no longer needed.
    for I := 0 to Resources.Count - 1 do
    begin
      Schedule := RetrieveSchedule(Resources[I], Date);
      CurrentSchedules.AddObject('', Schedule);
    end;

    // Now release all schedules no longer needed.  (Cross check CurrentSchedules
    // against Schedules list.)
    for I := ScheduleCount - 1 downto 0 do
    begin
      Schedule := Schedules[I];
      if CurrentSchedules.IndexOfObject(Schedule) = -1 then
        ReleaseSchedule(Schedule.SchedName, Schedule.SchedDate);
    end;
  finally
    CurrentSchedules.Free;
    FTimer.Enabled := True;
  end;
end;

procedure TJvTFAlarm.DestroyApptNotification(AAppt: TJvTFAppt);
begin
  FAlarmList.DeleteAppt(AAppt);
  inherited DestroyApptNotification(AAppt);
end;

procedure TJvTFAlarm.DisconnectSchedules;
begin
  ReleaseSchedules;
end;

function TJvTFAlarm.GetEnabled: Boolean;
begin
  Result := FTimer.Enabled;
end;

function TJvTFAlarm.GetTimerInterval: Integer;
begin
  Result := FTimer.Interval;
end;

function TJvTFAlarm.GetResources: TStrings;
begin
  Result := FResources;
end;

procedure TJvTFAlarm.InternalTimer(Sender: TObject);
begin
  if Trunc(Date) <> Trunc(FCurrentDate) then
  begin
    FCurrentDate := Date;
    ConnectSchedules;
  end;
  TimerCheck;
end;

procedure TJvTFAlarm.SetEnabled(Value: Boolean);
begin
  FTimer.Enabled := Value;
end;

procedure TJvTFAlarm.SetResources(Value: TStrings);
begin
  FResources.Assign(Value);
  ConnectSchedules;
end;

procedure TJvTFAlarm.SetTimerInterval(Value: Integer);
begin
  FTimer.Interval := Value;
end;

procedure TJvTFAlarm.TimerCheck;
begin
  AlarmCheck;
end;

//=== { TJvTFAlarmInfo } =====================================================

constructor TJvTFAlarmInfo.Create(AAppt: TJvTFAppt);
begin
  inherited Create;
  FAppt := AAppt;
end;

//=== { TJvTFAlarmList } =====================================================

procedure TJvTFAlarmList.AddAppt(AAppt: TJvTFAppt);
var
  AlarmInfo: TJvTFAlarmInfo;
begin
  if Assigned(AAppt) and (IndexOfAppt(AAppt) = -1) then
  begin
    AlarmInfo := TJvTFAlarmInfo.Create(AAppt);
    AlarmInfo.SnoozeMins := Owner.DefaultSnoozeMins;
    AlarmInfo.NextAlarmTime := AAppt.StartTime - AAppt.AlarmAdvance * ONE_MINUTE;
    AddObject(AAppt.ID, AlarmInfo);
  end;
end;

procedure TJvTFAlarmList.Clear;
var
  I: Integer;
begin
  for I := 0 to Count - 1 do
    Objects[I].Free;
  inherited Clear;
end;

procedure TJvTFAlarmList.DeleteAppt(AAppt: TJvTFAppt);
var
  I: Integer;
begin
  I := IndexOfAppt(AAppt);
  if I > -1 then
  begin
    Objects[I].Free;
    Delete(I);
  end;
end;

function TJvTFAlarmList.GetAlarmForAppt(AAppt: TJvTFAppt): TJvTFAlarmInfo;
begin
  Result := GetAlarmForApptID(AAppt.ID);
end;

function TJvTFAlarmList.GetAlarmForApptID(const ID: string): TJvTFAlarmInfo;
var
  I: Integer;
begin
  Result := nil;
  I := IndexOf(ID);
  if I > -1 then
    Result := TJvTFAlarmInfo(Objects[I]);
end;

function TJvTFAlarmList.IndexOfAppt(AAppt: TJvTFAppt): Integer;
begin
  Result := IndexOf(AAppt.ID);
end;

{$IFDEF UNITVERSIONING}
initialization
  RegisterUnitVersion(HInstance, UnitVersioning);

finalization
  UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}

end.
