unit TaskForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  TextEditor, ASKernel, Vcl.ComCtrls, Menus, SProgressIndicator, Vcl.ExtCtrls;

type
  TTaskFrm = class(TForm)
    pmTasks: TPopupMenu;
    mnuAbort: TMenuItem;
    mnuRemove: TMenuItem;
    N1: TMenuItem;
    mnuRemoveAll: TMenuItem;
    mnuSelChWarning: TMenuItem;
    N2: TMenuItem;
    mnuPause: TMenuItem;
    mnuResume: TMenuItem;
    ProgressWheel: TSProgressIndicator;
    Panel1: TPanel;
    teTasks: TTextEditor;
    StatusBar: TStatusBar;
    procedure FormShow(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure teTasksKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure AbortSelected(Sender: TObject);
    procedure mnuRemoveAllClick(Sender: TObject);
    procedure teTasksBeforeContextPopup(Sender: TObject);
    procedure teTasksListBoxSelect(Sender: TObject);
    procedure mnuPauseClick(Sender: TObject);
    procedure mnuResumeClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    function ProgressWheelDynGetFilled(Sender: TObject;
      out AOpacity: Double): Boolean;
  private
    FTasks: TArray<TASKernel.TWorkQueueItem>;
  public
    procedure UpdateTasks(const ATasks: TArray<TASKernel.TWorkQueueItem>;
      AKernel: TASKernel; AState: TExecStateEx; AError: Boolean);
  end;

var
  TaskFrm: TTaskFrm;

implementation

{$R *.dfm}

uses
  MainForm, Math, StrUtils, Rux;

function _scale(X: Integer): Integer;
begin
  Result := MulDiv(X, Screen.PixelsPerInch, 96);
end;

{ TTaskFrm }

procedure TTaskFrm.FormCreate(Sender: TObject);
begin
  teTasks.AddClass(
    MakeClass('Active task', teTasks.Font.Size, [fsBold], clWindowText)
  );
  teTasks.AddClass(
    MakeClass('Queued task', teTasks.Font.Size, [fsItalic], clGray)
  );
  teTasks.MarginLeft := _scale(8);
  teTasks.AddMenuItems(pmTasks.Items);
  teTasks.ListBoxHideSelection := True;
  teTasks.LineHighlight := False;
  TUx.RegisterCallback(
    ProgressWheel,
    procedure
    begin
      ProgressWheel.AccentColor := TUx.ThemeData.ActiveCaptionColor;
    end
  );
end;

procedure TTaskFrm.FormResize(Sender: TObject);
begin
  ProgressWheel.Width := Min(ProgressWheel.Height, _scale(250));
end;

procedure TTaskFrm.FormShow(Sender: TObject);
begin
  teTasks.Font.Assign(AlgosimMainForm.teConsole.Font);
end;

procedure TTaskFrm.mnuPauseClick(Sender: TObject);
begin
  if Length(FTasks) > 0 then
    FTasks[0].Kernel.Pause;
end;

procedure TTaskFrm.mnuRemoveAllClick(Sender: TObject);
begin
  if Length(FTasks) > 0 then
    FTasks[0].Kernel.AbortAll;
end;

procedure TTaskFrm.mnuResumeClick(Sender: TObject);
begin
  if Length(FTasks) > 0 then
    FTasks[0].Kernel.Resume;
end;

function TTaskFrm.ProgressWheelDynGetFilled(Sender: TObject;
  out AOpacity: Double): Boolean;
var
  LDiff: Int64;
begin
  LDiff := GetTickCount64 - TASKernel.SendMessageTimestamp;
  Result := LDiff < 200;
  if Result then
    AOpacity := 1.0 - LDiff / 200;
end;

procedure TTaskFrm.teTasksBeforeContextPopup(Sender: TObject);
begin
  mnuPause.Visible := (Length(FTasks) > 0) and (FTasks[0].Kernel.State = esRunning);
  mnuResume.Visible := (Length(FTasks) > 0) and (FTasks[0].Kernel.State = esPaused);
  mnuAbort.Visible := (Length(FTasks) > 0) and (teTasks.ListBoxItemIndex = 0);
  mnuRemove.Visible := (Length(FTasks) > 0) and (teTasks.ListBoxItemIndex > 0);
  mnuRemoveAll.Visible := Length(FTasks) > 1;
  mnuSelChWarning.Visible := False;
end;

procedure TTaskFrm.teTasksKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  case Key of
    VK_DELETE:
      begin
        AbortSelected(Sender);
        Key := 0;
      end;
  end;
end;

procedure TTaskFrm.teTasksListBoxSelect(Sender: TObject);
var
  Task: TASKernel.TWorkQueueItem;
  S: string;
begin
  if InRange(teTasks.ListBoxItemIndex, Low(FTasks), High(FTasks)) then
  begin
    Task := FTasks[teTasks.ListBoxItemIndex];
    S := Task.ToString;
    teTasks.ShowBalloon('Job Details', S, bikInfo, bpCaretPos, teTasks.CaretPos)
  end;
end;

procedure TTaskFrm.AbortSelected(Sender: TObject);
begin
  if InRange(teTasks.ListBoxItemIndex, Low(FTasks), High(FTasks)) then
    FTasks[teTasks.ListBoxItemIndex].Kernel.AbortJob(
      FTasks[teTasks.ListBoxItemIndex].UID
    );
end;

procedure TTaskFrm.UpdateTasks(const ATasks: TArray<TASKernel.TWorkQueueItem>;
  AKernel: TASKernel; AState: TExecStateEx; AError: Boolean);
var
  i: Integer;
  OldID: TGUID;
  NewID: TGUID;
  LState: TExecState;
begin

  if InRange(teTasks.ListBoxItemIndex, 0, High(FTasks)) then
    OldID := FTasks[teTasks.ListBoxItemIndex].UID
  else
    OldID := TGUID.Empty;

  if InRange(Ord(AState), Ord(Low(TExecState)), Ord(High(TExecState))) then
    LState := TExecState(Ord(AState))
  else if Assigned(AKernel) then
    LState := AKernel.State
  else
    Exit;

  FTasks := Copy(ATasks);

  teTasks.BeginVisualUpdate;
  try
    teTasks.NewFile;
    teTasks.BeginAddLine;
    try
      for i := 0 to High(FTasks) do
        teTasks.AddLine(IfThen(FTasks[i].Dequeued <> 0.0, '▶ ') + FTasks[i].Cmd +
          #32 + IfThen(i = 0, LState.ToString),
          IfThen(FTasks[i].Dequeued <> 0.0, 'Active task', 'Queued task'));
      if Length(FTasks) = 0 then
        teTasks.AddLine('(No tasks)', 'Queued task');
    finally
      teTasks.EndAddLine;
    end;
  finally
    teTasks.EndVisualUpdate(True);
  end;

  if InRange(teTasks.ListBoxItemIndex, 0, High(FTasks)) then
    OldID := FTasks[teTasks.ListBoxItemIndex].UID
  else
    OldID := TGUID.Empty;

  if (OldID <> TGUID.Empty) and (NewID <> TGUID.Empty) and (OldID <> NewID) then
  begin
    mnuAbort.Visible := False;
    mnuRemove.Visible := False;
    mnuSelChWarning.Visible := True;
  end
  else
  begin
    mnuAbort.Visible := (Length(FTasks) > 0) and (teTasks.ListBoxItemIndex = 0);
    mnuRemove.Visible := (Length(FTasks) > 0) and (teTasks.ListBoxItemIndex > 0);
  end;

  mnuRemoveAll.Visible := Length(FTasks) > 1;

  AlgosimMainForm.SetStatusText(LState.ToString, False);

  StatusBar.SimplePanel := True;
  StatusBar.SimpleText := Length(ATasks).ToString + #32'task(s)';

  if Ord(AState) = Ord(LState) then
  begin
    case LState of
      esReady:
        if AError then
          ProgressWheel.State := sFailureStopped
        else if ProgressWheel.State <> sFailureStopped then
          ProgressWheel.State := sStopped;
      esRunning:
        ProgressWheel.State := sRunning;
      esPausing:
        ProgressWheel.State := sPausing;
      esPaused:
        ProgressWheel.State := sPaused;
      esAborting:
        ProgressWheel.State := sStopping;
    end;
    OutputDebugString(PChar(LState.ToString));
  end;

end;

end.
