{$I JEDI.INC}

unit ScheduleEditor;

interface

uses
  SysUtils, Classes, Controls, Forms, StdCtrls, ComCtrls, ExtCtrls, AppEvnts,
  JclSchedule;

type
  TFrmScheduleEditor = class(TForm)
    pnlStartInfo: TPanel;
    lblStartDate: TLabel;
    lblStartTime: TLabel;
    lblStartCaption: TLabel;
    dtpStartDate: TDateTimePicker;
    dtpStartTime: TDateTimePicker;
    pnlEndInfo: TPanel;
    lblEndCaption: TLabel;
    rbInfinite: TRadioButton;
    rbTriggerCount: TRadioButton;
    rbDayCount: TRadioButton;
    rbDate: TRadioButton;
    lblEndTime: TLabel;
    dtpEndDate: TDateTimePicker;
    dtpEndTime: TDateTimePicker;
    edEventCount: TEdit;
    edDayCount: TEdit;
    pnlRecurringInfo: TPanel;
    lblScheduleType: TLabel;
    rbSingleShot: TRadioButton;
    rbDaily: TRadioButton;
    rbWeekly: TRadioButton;
    rbMonthly: TRadioButton;
    rbYearly: TRadioButton;
    bvlSeparation: TBevel;
    pnlDailySchedule: TPanel;
    lblDailyCaption: TLabel;
    rbDailyEveryWeekDay: TRadioButton;
    rbDailyInterval: TRadioButton;
    edDailyInterval: TEdit;
    lblDailyIntervalUnit: TLabel;
    pnlWeeklySchedule: TPanel;
    lblWeeklyCaption: TLabel;
    lblWeeklyInterval: TLabel;
    edWeeklyInterval: TEdit;
    lblWeeklyInterval2: TLabel;
    cbWeeklyMon: TCheckBox;
    cbWeeklyTue: TCheckBox;
    cbWeeklyWed: TCheckBox;
    cbWeeklyThu: TCheckBox;
    cbWeeklyFri: TCheckBox;
    cbWeeklySat: TCheckBox;
    cbWeeklySun: TCheckBox;
    pnlMonthlySchedule: TPanel;
    lblMonthlyCaption: TLabel;
    rbMonthlyDay: TRadioButton;
    edMonthlyEveryMonth: TEdit;
    lblMonthlyDayIntervalSuffix: TLabel;
    rbMonthlyEveryIndex: TRadioButton;
    cbMonthlyIndexValue: TComboBox;
    cbMonthlyIndexType: TComboBox;
    edMonthlyDay: TEdit;
    lblMontlhyDayInterval: TLabel;
    lblMonthlyIndexInterval: TLabel;
    edMonthlyIndexInterval: TEdit;
    lblMonthlyIndexIntervalSuffix: TLabel;
    bvlScheduleType: TBevel;
    pnlYearlySchedule: TPanel;
    lblYearlyCaption: TLabel;
    lblYearlyIntervalSuffix: TLabel;
    lblYearlyDateOf: TLabel;
    lblYearlyIndexInterval: TLabel;
    lblYearlyIndexIntervalSuffix: TLabel;
    rbYearlyDate: TRadioButton;
    edYearlyDateInterval: TEdit;
    rbYearlyIndex: TRadioButton;
    cbYearlyIndexValue: TComboBox;
    cbYearlyIndexKind: TComboBox;
    edYearlyDateDay: TEdit;
    edYearlyIndexInterval: TEdit;
    cbYearlyDateMonth: TComboBox;
    lblYearlyDateInterval: TLabel;
    lblYearlyIndexMonth: TLabel;
    cbYearlyIndexMonth: TComboBox;
    bvlDailyFreq: TBevel;
    pnlDailyFreq: TPanel;
    lblDailyFreq: TLabel;
    rbFreqOneshot: TRadioButton;
    dtpDayFreqOneshot: TDateTimePicker;
    rbFreqInterval: TRadioButton;
    edFreqInterval: TEdit;
    cbFreqIntervalUnit: TComboBox;
    lblFreqFrom: TLabel;
    dtpFreqFrom: TDateTimePicker;
    lblFreqTo: TLabel;
    dtpFreqTo: TDateTimePicker;
    Bevel1: TBevel;
    Bevel2: TBevel;
    AppEvents: TApplicationEvents;
    gbTestSettings: TGroupBox;
    cxStartToday: TCheckBox;
    cxCountMissedEvents: TCheckBox;
    btnTest: TButton;
    mmLog: TMemo;
    btnOk: TButton;
    btnCancel: TButton;
    procedure FormCreate(Sender: TObject);
    procedure btnTestClick(Sender: TObject);
    procedure AppEventsIdle(Sender: TObject; var Done: Boolean);
    procedure btnOkClick(Sender: TObject);
  private
    FTestSchedule: IJclSchedule;
    FSchedule: IJclSchedule;
    FBusy: Boolean;
    procedure SetSchedule(Value: IJclSchedule);
    procedure SelectRecurringInfoPage;
    procedure UpdateDailyPageInfo;
    procedure UpdateWeeklyPageInfo;
    procedure UpdateMonthlyPageInfo;
    procedure UpdateYearlyPageInfo;
    procedure UpdateFrequencyPageInfo;
    procedure UpdateEndPageInfo;
    procedure UpdateTestSettings;
    procedure InitSchedule(const ASchedule: IJclSchedule);
    procedure ScheduleToUI(const ASchedule: IJclSchedule);
  public
    property Schedule: IJclSchedule read FSchedule write SetSchedule;
  end;

var
  FrmScheduleEditor: TFrmScheduleEditor;

implementation

{$R *.dfm}

uses
  JclDateTime;

procedure DecodeTimeStampTime(const Stamp: TTimeStamp;
  var ADays, AHour, AMinute, ASecond, AMSec: Word);
var
  TempTime: Integer;
begin
  TempTime := Stamp.Time;
  AMSec := TempTime mod 1000;
  TempTime := TempTime div 1000;
  ASecond := TempTime mod 60;
  TempTime := TempTime div 60;
  AMinute := TempTime mod 60;
  TempTime := TempTime div 60;
  AHour := TempTime mod 24;
  TempTime := TempTime div 24;
  ADays := TempTime;
end;

function IndexOfRBChecked(const Controls: array of TRadioButton): Integer;
begin
  Result := High(Controls);
  while (Result >= 0) and not Controls[Result].Checked do
    Dec(Result);
end;

procedure TFrmScheduleEditor.SetSchedule(Value: IJclSchedule);
begin
  FSchedule := Value;
  ScheduleToUI(FSchedule);
end;

procedure TFrmScheduleEditor.SelectRecurringInfoPage;
begin
  pnlDailySchedule.Visible := rbDaily.Checked;
  pnlWeeklySchedule.Visible := rbWeekly.Checked;
  pnlMonthlySchedule.Visible := rbMonthly.Checked;
  pnlYearlySchedule.Visible := rbYearly.Checked;
end;

procedure TFrmScheduleEditor.UpdateDailyPageInfo;
begin
  edDailyInterval.Enabled := rbDailyInterval.Checked;
end;

procedure TFrmScheduleEditor.UpdateWeeklyPageInfo;
begin
end;

procedure TFrmScheduleEditor.UpdateMonthlyPageInfo;
begin
  edMonthlyDay.Enabled := rbMonthlyDay.Checked;
  edMonthlyEveryMonth.Enabled := rbMonthlyDay.Checked;

  cbMonthlyIndexValue.Enabled := rbMonthlyEveryIndex.Checked;
  cbMonthlyIndexType.Enabled := rbMonthlyEveryIndex.Checked;
  edMonthlyIndexInterval.Enabled := rbMonthlyEveryIndex.Checked;
end;

procedure TFrmScheduleEditor.UpdateYearlyPageInfo;
begin
  edYearlyDateDay.Enabled := rbYearlyDate.Checked;
  cbYearlyDateMonth.Enabled := rbYearlyDate.Checked;
  edYearlyDateInterval.Enabled := rbYearlyDate.Checked;

  cbYearlyIndexValue.Enabled := rbYearlyIndex.Checked;
  cbYearlyIndexKind.Enabled := rbYearlyIndex.Checked;
  cbYearlyIndexMonth.Enabled := rbYearlyIndex.Checked;
  edYearlyIndexInterval.Enabled := rbYearlyIndex.Checked;
end;

procedure TFrmScheduleEditor.UpdateFrequencyPageInfo;
begin
  pnlDailyFreq.Visible := not rbSingleShot.Checked;

  dtpDayFreqOneshot.Enabled := rbFreqOneshot.Checked;

  edFreqInterval.Enabled := rbFreqInterval.Checked;
  cbFreqIntervalUnit.Enabled := rbFreqInterval.Checked;
  dtpFreqFrom.Enabled := rbFreqInterval.Checked;
  dtpFreqTo.Enabled := rbFreqInterval.Checked;
end;

procedure TFrmScheduleEditor.UpdateEndPageInfo;
begin
  pnlEndInfo.Visible := not rbSingleShot.Checked;

  edEventCount.Enabled := rbTriggerCount.Checked;

  edDayCount.Enabled := rbDayCount.Checked;

  dtpEndDate.Enabled := rbDate.Checked;
  dtpEndTime.Enabled := rbDate.Checked;
end;

procedure TFrmScheduleEditor.UpdateTestSettings;
begin
  cxCountMissedEvents.Enabled := cxStartToday.Checked;
end;

procedure TFrmScheduleEditor.InitSchedule(const ASchedule: IJclSchedule);
var
  TempDOW: TScheduleWeekDays;
begin
  with ASchedule do
  begin
    RecurringType := TScheduleRecurringKind(IndexOfRBChecked([rbSingleShot, rbDaily, rbWeekly,
      rbMonthly, rbYearly]));
    StartDate := DateTimeToTimeStamp(Trunc(dtpStartDate.Date) + Frac(dtpStartTime.Time));
    EndType := TScheduleEndKind(IndexOfRBChecked([rbInfinite, rbDate, rbTriggerCount, rbDayCount]));
    if RecurringType = srkOneShot then
    begin
      EndType := sekDate;
      EndDate := StartDate;
      with ASchedule as IJclScheduleDayFrequency do
      begin
        StartTime := StartDate.Time;
        EndTime := EndDate.Time;
        Interval := 1;
      end;
    end
    else
    begin
      case RecurringType of
        srkDaily:
          begin
            with ASchedule as IJclDailySchedule do
            begin
              EveryWeekDay := rbDailyEveryWeekDay.Checked;
              if not EveryWeekDay then
                Interval := StrToInt64(edDailyInterval.Text);
            end;
          end;
        srkWeekly:
          begin
            with ASchedule as IJclWeeklySchedule do
            begin
              TempDOW := [];
              if cbWeeklyMon.Checked then
                Include(TempDOW, swdMonday);
              if cbWeeklyTue.Checked then
                Include(TempDOW, swdTuesday);
              if cbWeeklyWed.Checked then
                Include(TempDOW, swdWednesday);
              if cbWeeklyThu.Checked then
                Include(TempDOW, swdThursday);
              if cbWeeklyFri.Checked then
                Include(TempDOW, swdFriday);
              if cbWeeklySat.Checked then
                Include(TempDOW, swdSaturday);
              if cbWeeklySun.Checked then
                Include(TempDOW, swdSunday);
              DaysOfWeek := TempDOW;
              Interval := StrToInt64(edWeeklyInterval.Text);
            end;
          end;
        srkMonthly:
          begin
            with ASchedule as IJclMonthlySchedule do
            begin
              if rbMonthlyDay.Checked then
              begin
                IndexKind := sikNone;
                Day := StrToInt64(edMonthlyDay.Text);
                Interval := StrToInt64(edMonthlyEveryMonth.Text);
              end
              else
              begin
                IndexKind := TScheduleIndexKind(cbMonthlyIndexType.ItemIndex + 1);
                if cbMonthlyIndexValue.ItemIndex > -1 then
                begin
                  if cbMonthlyIndexValue.ItemIndex < 4 then
                    IndexValue := cbMonthlyIndexValue.ItemIndex + 1
                  else
                    IndexValue := sivLast;
                end
                else
                  IndexValue := StrToInt64(cbMonthlyIndexValue.Text);
                Interval := StrToInt64(edMonthlyIndexInterval.Text);
              end;
            end;
          end;
        srkYearly:
          begin
            with ASchedule as IJclYearlySchedule do
            begin
              if rbYearlyDate.Checked then
              begin
                IndexKind := sikNone;
                Day := StrToInt64(edYearlyDateDay.Text);
                Month := cbYearlyDateMonth.ItemIndex + 1;
                Interval := StrToInt64(edYearlyDateInterval.Text);
              end
              else
              begin
                IndexKind := TScheduleIndexKind(cbYearlyIndexKind.ItemIndex + 1);
                if cbYearlyIndexValue.ItemIndex > -1 then
                begin
                  if cbYearlyIndexValue.ItemIndex < 4 then
                    IndexValue := cbYearlyIndexValue.ItemIndex + 1
                  else
                    IndexValue := sivLast;
                end
                else
                  IndexValue := StrToInt64(cbYearlyIndexValue.Text);
                Month := cbYearlyIndexMonth.ItemIndex + 1;
                Interval := StrToInt64(edYearlyIndexInterval.Text);
              end;
            end;
          end;
      end;

      with ASchedule as IJclScheduleDayFrequency do
      begin
        if rbFreqOneshot.Checked then
        begin
          StartTime := DateTimeToTimeStamp(dtpDayFreqOneshot.Time).Time;
          EndTime := StartTime;
          Interval := 1;
        end
        else
        begin
          StartTime := DateTimeToTimeStamp(dtpFreqFrom.Time).Time;
          EndTime := DateTimeToTimeStamp(dtpFreqTo.Time).Time;
          case cbFreqIntervalUnit.ItemIndex of
            0: { Milliseconds }
              Interval := StrToInt64(edFreqInterval.Text);
            1: { Seconds }
              Interval := 1000 * StrToInt64(edFreqInterval.Text);
            2: { Minutes }
              Interval := 60 * 1000 * StrToInt64(edFreqInterval.Text);
            3: { Hours }
              Interval := 60 * 60 * 1000 * StrToInt64(edFreqInterval.Text);
          end;
        end;
      end;

      case EndType of
        sekDate:
          EndDate := DateTimeToTimeStamp(Trunc(dtpEndDate.Date) + Frac(dtpEndTime.Time));
        sekTriggerCount:
          EndCount := StrToInt64(edEventCount.Text);
        sekDayCount:
          EndCount := StrToInt64(edDayCount.Text);
      end;
    end;
    Reset;
  end;
end;

procedure TFrmScheduleEditor.ScheduleToUI(const ASchedule: IJclSchedule);
var
  TempStamp: TTimeStamp;
begin
  with ASchedule do
  begin
    dtpStartDate.Date := Trunc(TimeStampToDateTime(StartDate));
    dtpStartTime.Time := Frac(TimeStampToDateTime(StartDate));

    case RecurringType of
      srkOneShot:
        rbSingleShot.Checked := True;
      srkDaily:
        begin
          rbDaily.Checked := True;
          with ASchedule as IJclDailySchedule do
          begin
            if EveryWeekDay then
              rbDailyEveryWeekDay.Checked := True
            else
            begin
              rbDailyInterval.Checked := True;
              edDailyInterval.Text := IntToStr(Interval);
            end;
          end;
        end;
      srkWeekly:
        begin
          rbWeekly.Checked := True;
          with ASchedule as IJclWeeklySchedule do
          begin
            cbWeeklyMon.Checked := swdMonday in DaysOfWeek;
            cbWeeklyTue.Checked := swdTuesday in DaysOfWeek;
            cbWeeklyWed.Checked := swdWednesday in DaysOfWeek;
            cbWeeklyThu.Checked := swdThursday in DaysOfWeek;
            cbWeeklyFri.Checked := swdFriday in DaysOfWeek;
            cbWeeklySat.Checked := swdSaturday in DaysOfWeek;
            cbWeeklySun.Checked := swdSunday in DaysOfWeek;
            edWeeklyInterval.Text := IntToStr(Interval);
          end
        end;
      srkMonthly:
        begin
          rbMonthly.Checked := True;
          with ASchedule as IJclMonthlySchedule do
          begin
            case IndexKind of
              sikNone:
                begin
                  rbMonthlyDay.Checked := True;
                  edMonthlyDay.Text := IntToStr(Day);
                  edMonthlyEveryMonth.Text := IntToStr(Interval);
                end;
              sikDay, sikWeekDay, sikWeekendDay, sikMonday, sikTuesday,
              sikWednesday, sikThursday, sikFriday, sikSaturday,
              sikSunday:
                begin
                  rbMonthlyEveryIndex.Checked := True;
                  if (IndexValue > 0) and (IndexValue < 5) then
                    cbMonthlyIndexValue.ItemIndex := IndexValue - 1
                  else
                  if IndexValue = sivLast then
                    cbMonthlyIndexValue.ItemIndex := 4
                  else
                  begin
                    cbMonthlyIndexValue.ItemIndex := -1;
                    cbMonthlyIndexValue.Text := IntToStr(IndexValue);
                  end;
                  cbMonthlyIndexType.ItemIndex := Ord(IndexKind) - 1;
                  edMonthlyIndexInterval.Text := IntToStr(Interval);
                end;
            else
              raise ESchedule.Create('Invalid schedule settings found.');
            end;
          end;
        end;
      srkYearly:
        begin
          rbYearly.Checked := True;
          with ASchedule as IJclYearlySchedule do
          begin
            case IndexKind of
              sikNone:
                begin
                  rbYearlyDate.Checked := True;
                  edYearlyDateDay.Text := IntToStr(Day);
                  cbYearlyDateMonth.ItemIndex := Month - 1;
                  edYearlyDateInterval.Text := IntToStr(Interval);
                end;
              sikDay, sikWeekDay, sikWeekendDay, sikMonday, sikTuesday,
                sikWednesday, sikThursday, sikFriday, sikSaturday,
                sikSunday:
                begin
                  rbYearlyIndex.Checked := True;
                  if (IndexValue > 0) and (IndexValue < 5) then
                    cbYearlyIndexValue.ItemIndex := IndexValue - 1
                  else
                  if IndexValue = sivLast then
                    cbYearlyIndexValue.ItemIndex := 4
                  else
                  begin
                    cbYearlyIndexValue.ItemIndex := -1;
                    cbYearlyIndexValue.Text := IntToStr(IndexValue);
                  end;
                  cbYearlyIndexKind.ItemIndex := Ord(IndexKind) - 1;
                  cbYearlyIndexMonth.ItemIndex := Month - 1;
                  edYearlyIndexInterval.Text := IntToStr(Interval);
                end;
            else
              raise ESchedule.Create('Invalid schedule settings found.');
            end;
          end;
        end;
    end;
    case EndType of
      sekNone:
        rbInfinite.Checked := True;
      sekDate:
        begin
          rbDate.Checked := True;
          dtpEndDate.Date := Trunc(TimeStampToDateTime(EndDate));
          dtpEndTime.Time := Frac(TimeStampToDateTime(EndDate));
        end;
      sekTriggerCount:
        begin
          rbTriggerCount.Checked := True;
          edEventCount.Text := IntToStr(EndCount);
        end;
      sekDayCount:
        begin
          rbDayCount.Checked := True;
          edDayCount.Text := IntToStr(EndCount);
        end;
    end;
    if RecurringType <> srkOneShot then
    begin
      with ASchedule as IJclScheduleDayFrequency do
      begin
        rbFreqOneshot.Checked := StartTime = EndTime;
        rbFreqInterval.Checked := StartTime <> EndTime;
        if rbFreqOneshot.Checked then
        begin
          TempStamp := DateTimeToTimeStamp(Now);
          TempStamp.Time := StartTime;
          dtpDayFreqOneshot.Time := TimeStampToDateTime(TempStamp);
        end
        else
        begin
          TempStamp := DateTimeToTimeStamp(Now);
          TempStamp.Time := StartTime;
          dtpFreqFrom.Time := TimeStampToDateTime(TempStamp);
          TempStamp.Time := EndTime;
          dtpFreqTo.Time := TimeStampToDateTime(TempStamp);

          if Interval mod (60 * 60 * 1000) = 0 then
          begin
            cbFreqIntervalUnit.ItemIndex := 3;
            edFreqInterval.Text := IntToStr(Interval div (60 * 60 * 1000));
          end
          else
          if Interval mod (60 * 1000) = 0 then
          begin
            cbFreqIntervalUnit.ItemIndex := 2;
            edFreqInterval.Text := IntToStr(Interval div (60 * 1000));
          end
          else
          if Interval mod 1000 = 0 then
          begin
            cbFreqIntervalUnit.ItemIndex := 1;
            edFreqInterval.Text := IntToStr(Interval div 1000);
          end
          else
          begin
            cbFreqIntervalUnit.ItemIndex := 0;
            edFreqInterval.Text := IntToStr(Interval);
          end;
        end;
      end;
    end;
  end;
end;

procedure TFrmScheduleEditor.FormCreate(Sender: TObject);
begin
  FTestSchedule := CreateSchedule;
  {$IFNDEF COMPILER6_UP}
  dtpStartDate.DateFormat := dfShort;
  dtpEndDate.DateFormat := dfShort;
  {$ELSE}
  dtpStartDate.Format := 'dd-MM-yyyy';
  dtpStartTime.Format := 'HH:mm:ss';
  dtpEndDate.Format := 'dd-MM-yyyy';
  dtpEndTime.Format := 'HH:mm:ss';
  dtpDayFreqOneshot.Format := 'HH:mm:ss';
  dtpFreqFrom.Format := 'HH:mm:ss';
  dtpFreqTo.Format := 'HH:mm:ss';
  {$ENDIF}
  dtpStartDate.DateTime := Now;
  dtpEndDate.DateTime := Now;

  cbMonthlyIndexValue.ItemIndex := 0;
  cbMonthlyIndexType.ItemIndex := 1;
  cbYearlyIndexValue.ItemIndex := 0;
  cbYearlyIndexKind.ItemIndex := 1;
  cbYearlyDateMonth.ItemIndex := 0;
  cbYearlyIndexMonth.ItemIndex := 0;
  cbFreqIntervalUnit.ItemIndex := 2;
end;

procedure TFrmScheduleEditor.btnTestClick(Sender: TObject);
var
  Stamp: TTimeStamp;
  AYear, AMonth, ADay: Word;
  ADays, AHour, AMinute, ASecond, AMSec: Word;
begin
  if FBusy then
    FBusy := False
  else
  begin
    try
      FBusy := True;
      btnTest.Caption := 'Stop';
      btnOk.Enabled := False;
      btnCancel.Enabled := False;
      InitSchedule(FTestSchedule);
      mmLog.Lines.Clear;
      if cxStartToday.Checked then
        Stamp := FTestSchedule.NextEventFromNow(cxCountMissedEvents.Checked)
      else
        Stamp := FTestSchedule.NextEventFrom(FTestSchedule.StartDate, True);
      while (Stamp.Date > 0) and FBusy do
      begin
        JclDateTime.DecodeDate(TimeStampToDateTime(Stamp), AYear, AMonth, ADay);
        DecodeTimeStampTime(Stamp, ADays, AHour, AMinute, ASecond, AMSec);
        mmLog.Lines.Add(Format('%.5d (%.4d): %.2d-%.2d-%.4d@%.2d:%.2d:%.2d.%.3d',
          [FTestSchedule.TriggerCount, FTestSchedule.DayCount, ADay, AMonth,
           AYear, AHour, AMinute, ASecond, AMSec]));
        Application.ProcessMessages;
        Stamp := FTestSchedule.NextEvent(True);
      end;
    finally
      FBusy := False;
      btnTest.Caption := 'Run';
      btnOk.Enabled := True;
      btnCancel.Enabled := True;
    end;
  end;
end;

procedure TFrmScheduleEditor.AppEventsIdle(Sender: TObject;
  var Done: Boolean);
begin
  SelectRecurringInfoPage;
  if rbDaily.Checked then
    UpdateDailyPageInfo
  else
  if rbWeekly.Checked then
    UpdateWeeklyPageInfo
  else
  if rbMonthly.Checked then
    UpdateMonthlyPageInfo
  else
  if rbYearly.Checked then
    UpdateYearlyPageInfo;
  UpdateFrequencyPageInfo;
  UpdateEndPageInfo;

  UpdateTestSettings;
end;

procedure TFrmScheduleEditor.btnOkClick(Sender: TObject);
begin
  InitSchedule(Schedule);
end;

end.

