Blame main.pas

f754e5
unit main;
f754e5
f754e5
{$mode objfpc}{$H+}
f754e5
f754e5
interface
f754e5
f754e5
uses
8a9c8e
  SysUtils, DateUtils, Forms, Controls, Graphics, ExtCtrls, StdCtrls;
f754e5
f754e5
type
f754e5
f754e5
  PTodoHistoryItem = ^TTodoHistoryItem;
f754e5
  TTodoHistoryItem = record
f754e5
    date          : TDate;
f754e5
    done          : Boolean;
f754e5
    doneByOthers  : Boolean;
f754e5
  end;
f754e5
f754e5
  PTodoItem = ^TTodoItem;
f754e5
  TTodoItem = record
f754e5
    id            : Integer;
f754e5
    name          : string;
f754e5
    repeatDays    : Integer;
f754e5
    daysSinceDone : Integer;
f754e5
    done          : Boolean;
f754e5
    doneByOthers  : Boolean;
f754e5
    cancelled     : Boolean;
8a9c8e
    planned       : Boolean;
8a9c8e
    notPlanned    : Boolean;
f754e5
    history       : array of TTodoHistoryItem;
f754e5
    panel         : TPanel;
f754e5
    updating      : Boolean;
f754e5
  end;
f754e5
f754e5
  PTodoList = ^TTodoList;
f754e5
  TTodoList = record
f754e5
    filename      : string;
f754e5
    name          : string;
f754e5
    nextId        : Integer;
f754e5
    date          : TDate;
f754e5
    items         : array of TTodoItem;
f754e5
  end;
f754e5
f754e5
  { TfrmMain }
f754e5
f754e5
  TfrmMain = class(TForm)
f754e5
    bAdd: TButton;
f754e5
    bNextDay: TButton;
f754e5
    cbPlanned: TCheckBox;
f754e5
    lDate: TLabel;
f754e5
    sbTodo: TScrollBox;
f754e5
    procedure bAddClick(Sender: TObject);
f754e5
    procedure bNextDayClick(Sender: TObject);
f754e5
    procedure cbPlannedChange(Sender: TObject);
f754e5
    procedure FormActivate(Sender: TObject);
f754e5
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
f754e5
    procedure OnChangeItem(Sender: TObject);
f754e5
    procedure UpdateItem(var item: TTodoItem);
f754e5
    procedure ClearTodoList();
f754e5
    procedure RebuildTodoList();
f754e5
  private
f754e5
  public
f754e5
  end;
f754e5
var
f754e5
  frmMain: TfrmMain;
f754e5
  todoList: TTodoList;
f754e5
f754e5
implementation
f754e5
f754e5
{$R *.lfm}
f754e5
f754e5
f754e5
function BoolToInt(b: Boolean): Integer;
f754e5
begin
f754e5
  if b then Result := 1 else Result := 0;
f754e5
end;
f754e5
f754e5
f754e5
function DateToInt(date: TDate): Integer;
f754e5
var
f754e5
   y, m, d: Word;
f754e5
begin
f754e5
  DecodeDate(date, y, m, d);
f754e5
  Result := y*10000 + m*100 + d;
f754e5
end;
f754e5
f754e5
f754e5
function IntToDate(i: Integer): TDate;
f754e5
begin
f754e5
  Result := EncodeDate(i div 10000, (i div 100) mod 100, i mod 100);
f754e5
end;
f754e5
f754e5
f754e5
function RoundDate(date: TDate): TDate;
f754e5
begin
f754e5
  Result := IntToDate( DateToInt(date) );
f754e5
end;
f754e5
f754e5
8a9c8e
function isPlanned(var item: TTodoItem): Boolean;
8a9c8e
begin
8a9c8e
  Result := item.planned or ((item.daysSinceDone >= item.repeatDays) and not item.notPlanned);
8a9c8e
end;
8a9c8e
8a9c8e
8a9c8e
function isDone(var item: TTodoItem): Boolean;
8a9c8e
begin
8a9c8e
  Result := item.done or item.doneByOthers;
8a9c8e
end;
8a9c8e
8a9c8e
8a9c8e
function isNeedToDo(var item: TTodoItem): Boolean;
8a9c8e
begin
8a9c8e
  Result := isPlanned(item) and (not item.cancelled) and not isDone(item);
8a9c8e
end;
8a9c8e
8a9c8e
f754e5
procedure ClearTodoList(var list: TTodoList);
f754e5
begin
f754e5
  list.filename := '';
f754e5
  list.name := '';
f754e5
  list.nextId := 0;
f754e5
  setLength(list.items, 0);
f754e5
end;
f754e5
f754e5
f754e5
function SaveTodoList(var list: TTodoList; filename: string): Boolean;
f754e5
var
f754e5
  i: Integer;
f754e5
  f: textfile;
f754e5
begin
f754e5
  Result := False;
f754e5
f754e5
  AssignFile(f, filename);
f754e5
  Rewrite(f);
8a9c8e
  Writeln(f, 2); // version
f754e5
  Writeln(f, list.name);
f754e5
  Writeln(f, list.nextId);
f754e5
  Writeln(f, DateToInt(list.date));
f754e5
  Writeln(f, Length(list.items));
f754e5
  for i := 0 to Length(list.items)-1 do begin
f754e5
    Writeln(f, list.items[i].id);
f754e5
    Writeln(f, list.items[i].name);
f754e5
    Writeln(f,
f754e5
      list.items[i].repeatDays, ' ',
f754e5
      list.items[i].daysSinceDone, ' ',
f754e5
      BoolToInt(list.items[i].done), ' ',
f754e5
      BoolToInt(list.items[i].doneByOthers), ' ',
8a9c8e
      BoolToInt(list.items[i].cancelled), ' ',
f754e5
      BoolToInt(list.items[i].planned), ' ',
8a9c8e
      BoolToInt(list.items[i].notPlanned) );
f754e5
  end;
f754e5
  CloseFile(f);
f754e5
f754e5
  Result := True;
f754e5
end;
f754e5
f754e5
f754e5
function SaveTodoListEx(var list: TTodoList): Boolean;
f754e5
var
f754e5
  i: Integer;
f754e5
  fo, fn: string;
f754e5
begin
f754e5
  Result := False;
f754e5
  if FileExists(list.filename) then begin
f754e5
    for i := 0 to 1000 do begin
f754e5
      fo := list.filename + '.old.' + IntToStr(i);
f754e5
      if FileExists(fo) then fo := '' else break;
f754e5
    end;
f754e5
    for i := 0 to 1000 do begin
f754e5
      fn := list.filename + '.new.' + IntToStr(i);
f754e5
      if FileExists(fn) then fn := '' else break;
f754e5
    end;
f754e5
    if (fo = '') or (fn = '') then Exit;
f754e5
f754e5
    if not SaveTodoList(list, fn) then begin
f754e5
      DeleteFile(fn);
f754e5
      Exit;
f754e5
    end;
f754e5
f754e5
    if not RenameFile(list.filename, fo) then begin
f754e5
      DeleteFile(fn);
f754e5
      Exit;
f754e5
    end;
f754e5
f754e5
    if not RenameFile(fn, list.filename) then begin
f754e5
      RenameFile(fo, list.filename);
f754e5
      DeleteFile(fn);
f754e5
      Exit;
f754e5
    end;
f754e5
f754e5
    DeleteFile(fn);
f754e5
    DeleteFile(fo);
f754e5
  end else
f754e5
  if not SaveTodoList(list, list.filename) then begin
f754e5
    DeleteFile(list.filename);
f754e5
    Exit;
f754e5
  end;
f754e5
  Result := True;
f754e5
end;
f754e5
f754e5
f754e5
function LoadTodoList(var list: TTodoList; filename: string): Boolean;
f754e5
var
8a9c8e
  i, version, count: Integer;
8a9c8e
  i0, i1, i2, i3, i4: Integer;
f754e5
  f: textfile;
f754e5
begin
f754e5
  clearTodoList(list);
f754e5
  list.filename := filename;
f754e5
f754e5
  Result := False;
f754e5
  if not FileExists(filename) then Exit;
f754e5
f754e5
  AssignFile(f, filename);
f754e5
  Reset(f);
8a9c8e
  Readln(f, version);
f754e5
  Readln(f, list.name);
f754e5
  Readln(f, list.nextId);
f754e5
  Readln(f, i0);
f754e5
  list.date := IntToDate(i0);
f754e5
  Readln(f, count);
f754e5
  setLength(list.items, count);
f754e5
  for i := 0 to count-1 do begin
f754e5
    Readln(f, list.items[i].id);
f754e5
    Readln(f, list.items[i].name);
8a9c8e
    if version <= 1 then begin
8a9c8e
      Readln(f,
8a9c8e
        list.items[i].repeatDays,
8a9c8e
        list.items[i].daysSinceDone,
8a9c8e
        i0, i1, i2, i3 );
8a9c8e
      list.items[i].done         := i0 <> 0;
8a9c8e
      list.items[i].doneByOthers := i1 <> 0;
8a9c8e
      list.items[i].cancelled    := i3 <> 0;
8a9c8e
      list.items[i].planned      := i2 <> 0;
8a9c8e
      list.items[i].notPlanned   := False;
8a9c8e
    end else begin
8a9c8e
      Readln(f,
8a9c8e
        list.items[i].repeatDays,
8a9c8e
        list.items[i].daysSinceDone,
8a9c8e
        i0, i1, i2, i3, i4 );
8a9c8e
      list.items[i].done         := i0 <> 0;
8a9c8e
      list.items[i].doneByOthers := i1 <> 0;
8a9c8e
      list.items[i].cancelled    := i2 <> 0;
8a9c8e
      list.items[i].planned      := i3 <> 0;
8a9c8e
      list.items[i].notPlanned   := i4 <> 0;
8a9c8e
    end;
f754e5
  end;
f754e5
  CloseFile(f);
f754e5
  Result := True;
f754e5
end;
f754e5
f754e5
f754e5
procedure SetTodoListDate(var list: TTodoList; date: TDate);
f754e5
var
f754e5
  i, d: Integer;
f754e5
begin
f754e5
  date := RoundDate(date);
f754e5
  d := DaysBetween(date, list.date);
f754e5
  if (date < list.date) and (d > 0) then d := -d;
f754e5
  list.date := date;
f754e5
  if d = 0 then Exit;
f754e5
  for i := 0 to Length(list.items)-1 do begin
f754e5
    Inc(list.items[i].daysSinceDone, d);
f754e5
    if d < 0 then begin
f754e5
      if list.items[i].daysSinceDone < 1 then
f754e5
         list.items[i].daysSinceDone := 1;
f754e5
    end else begin
8a9c8e
      if isDone(list.items[i]) then begin
f754e5
        list.items[i].daysSinceDone := d;
f754e5
        list.items[i].planned := False;
f754e5
      end;
f754e5
      list.items[i].done := False;
f754e5
      list.items[i].doneByOthers := False;
8a9c8e
      list.items[i].cancelled := False;
8a9c8e
      list.items[i].notPlanned := False;
f754e5
    end
f754e5
  end
f754e5
end;
f754e5
f754e5
f754e5
procedure AddTodoItem(var list: TTodoList);
f754e5
var
f754e5
  i: Integer;
f754e5
begin
f754e5
  i := Length(list.items);
f754e5
  SetLength(list.items, i + 1);
f754e5
  list.items[i].id := list.nextId;
f754e5
  list.items[i].repeatDays := 1;
f754e5
  list.items[i].daysSinceDone := 1;
f754e5
  Inc(list.nextId);
f754e5
end;
f754e5
f754e5
f754e5
procedure RemoveTodoItem(var list: TTodoList; i: Integer);
f754e5
var
f754e5
  j: Integer;
f754e5
begin
f754e5
  if (i < 0) or (i >= Length(list.items)) then Exit;
f754e5
  for j := i+1 to Length(list.items)-1 do
f754e5
    list.items[j-1] := list.items[j];
f754e5
  SetLength(list.items, Length(list.items) - 1);
f754e5
end;
f754e5
f754e5
f754e5
procedure InsertTodoItem(var list: TTodoList; i: Integer; item: TTodoItem);
f754e5
var
f754e5
  j: Integer;
f754e5
begin
f754e5
  if i < 0 then i := 0;
f754e5
  if i > Length(list.items) then i := Length(list.items);
f754e5
  SetLength(list.items, Length(list.items) + 1);
f754e5
  for j := Length(list.items)-1 downto i+1 do
f754e5
    list.items[j] := list.items[j-1];
f754e5
  list.items[i] := item;
f754e5
end;
f754e5
f754e5
f754e5
procedure TfrmMain.bAddClick(Sender: TObject);
f754e5
begin
f754e5
  AddTodoItem(todoList);
f754e5
  RebuildTodoList();
f754e5
end;
f754e5
f754e5
procedure TfrmMain.bNextDayClick(Sender: TObject);
f754e5
begin
f754e5
  SetTodoListDate(todoList, IncDay(todoList.date));
f754e5
  RebuildTodoList();
f754e5
end;
f754e5
f754e5
procedure TfrmMain.cbPlannedChange(Sender: TObject);
f754e5
begin
f754e5
  RebuildTodoList();
f754e5
end;
f754e5
f754e5
procedure TfrmMain.FormActivate(Sender: TObject);
f754e5
begin
f754e5
  if todoList.filename <> '' then Exit;
f754e5
  LoadTodoList(todoList, 'todo.list');
f754e5
  SetTodoListDate(todoList, Date);
f754e5
  RebuildTodoList();
f754e5
end;
f754e5
f754e5
f754e5
procedure TfrmMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
f754e5
begin
f754e5
  SaveTodoListEx(todoList);
8a9c8e
  CloseAction := caFree;
f754e5
end;
f754e5
f754e5
f754e5
procedure TfrmMain.OnChangeItem(Sender: TObject);
f754e5
var
f754e5
  p: TPanel;
f754e5
  i: Integer;
f754e5
  n: string;
f754e5
  item: TTodoItem;
f754e5
f754e5
  function ParseInt(s: string): Integer;
f754e5
  begin
f754e5
    Result := StrToInt(s);
f754e5
    if Result < 1 then Result := 1;
f754e5
  end;
f754e5
f754e5
begin
f754e5
  p := TPanel(TControl(Sender).Parent);
f754e5
  i := p.Tag;
f754e5
f754e5
  n := TControl(Sender).Name;
f754e5
  if n = 'delete' then begin
f754e5
    RemoveTodoItem(todoList, i);
f754e5
    RebuildTodoList();
f754e5
  end else
f754e5
  if n = 'up' then begin
f754e5
    item := todoList.items[i];
f754e5
    RemoveTodoItem(todoList, i);
f754e5
    InsertTodoItem(todoList, i-1, item);
f754e5
    RebuildTodoList();
f754e5
  end else
f754e5
  if n = 'down' then begin
f754e5
    item := todoList.items[i];
f754e5
    RemoveTodoItem(todoList, i);
f754e5
    InsertTodoItem(todoList, i+1, item);
f754e5
    RebuildTodoList();
f754e5
  end else begin
f754e5
    with todoList.items[i] do begin
f754e5
      if updating then Exit;
f754e5
      name          := TEdit( p.FindChildControl('name') ).Text;
f754e5
      repeatDays    := ParseInt(TEdit( p.FindChildControl('repeatDays')    ).Text);
f754e5
      daysSinceDone := ParseInt(TEdit( p.FindChildControl('daysSinceDone') ).Text);
f754e5
      done          := TCheckBox( p.FindChildControl('done')         ).Checked;
f754e5
      doneByOthers  := TCheckBox( p.FindChildControl('doneByOthers') ).Checked;
f754e5
      cancelled     := TCheckBox( p.FindChildControl('cancelled')    ).Checked;
8a9c8e
      planned       := TCheckBox( p.FindChildControl('planned')      ).Checked;
8a9c8e
      notPlanned    := TCheckBox( p.FindChildControl('notPlanned')   ).Checked;
f754e5
    end;
f754e5
    UpdateItem(todoList.items[i]);
f754e5
  end;
f754e5
f754e5
  SaveTodoListEx(todoList);
f754e5
end;
f754e5
f754e5
f754e5
procedure TfrmMain.UpdateItem(var item: TTodoItem);
f754e5
begin
f754e5
  item.updating := True;
f754e5
  with item.panel do begin
f754e5
    if item.cancelled then
f754e5
      item.panel.Font.StrikeThrough := True
f754e5
    else
f754e5
      item.panel.Font.StrikeThrough := False;
f754e5
8a9c8e
    if isNeedToDo(item) then
8a9c8e
      item.panel.Font.Color := clDefault
f754e5
    else
8a9c8e
      item.panel.Font.Color := clGrayText;
f754e5
f754e5
    TCheckBox( FindChildControl('done')         ).Checked := item.done;
f754e5
    TCheckBox( FindChildControl('doneByOthers') ).Checked := item.doneByOthers;
f754e5
    TCheckBox( FindChildControl('cancelled')    ).Checked := item.cancelled;
8a9c8e
    TCheckBox( FindChildControl('planned')      ).Checked := item.planned;
8a9c8e
    TCheckBox( FindChildControl('notPlanned')   ).Checked := item.notPlanned;
f754e5
    TEdit( FindChildControl('daysSinceDone') ).Text := IntToStr(item.daysSinceDone);
f754e5
    TEdit( FindChildControl('repeatDays')    ).Text := IntToStr(item.repeatDays);
f754e5
    TEdit( FindChildControl('name')          ).Text := item.name;
f754e5
  end;
f754e5
  item.updating := False;
f754e5
end;
f754e5
f754e5
f754e5
procedure TfrmMain.ClearTodoList();
f754e5
var
f754e5
  i: Integer;
f754e5
begin
f754e5
  while sbTodo.ControlCount > 0 do
f754e5
    sbTodo.Controls[0].Destroy();
f754e5
  for i := 0 to Length(todoList.items)-1 do
f754e5
    todoList.items[i].panel := nil;
f754e5
  sbTodo.UpdateScrollbars;
f754e5
end;
f754e5
f754e5
f754e5
procedure TfrmMain.RebuildTodoList();
f754e5
const
8a9c8e
  b = 0;
8a9c8e
  bb = 2;
f754e5
  sb = 16;
f754e5
var
8a9c8e
  i, j, h, t, l, ll, r: Integer;
f754e5
  scroll: Integer;
8a9c8e
  plannedOnly: Boolean;
f754e5
  p: TPanel;
f754e5
f754e5
  function createCheckBox(name, title: string): TCheckBox;
f754e5
  begin
f754e5
    Result := TCheckBox.Create(p);
f754e5
    Result.Name := name;
f754e5
    Result.Caption := '';
f754e5
    Result.Hint := title;
f754e5
    Result.ShowHint := True;
f754e5
    Result.Top := bb;
f754e5
    Result.Left := l;
f754e5
    Result.OnChange := @OnChangeItem;
f754e5
    p.InsertControl(Result);
f754e5
    Inc(l, Result.Width);
f754e5
  end;
f754e5
f754e5
  function createButton(name, title: string): TButton;
f754e5
  begin
f754e5
    Result := TButton.Create(p);
f754e5
    Result.Name := name;
f754e5
    Result.Caption := title;
f754e5
    Result.ParentFont := True;
f754e5
    Result.Top := bb;
f754e5
    Result.Left := r - Result.Width;
f754e5
    Result.Anchors := [akTop, akRight];
f754e5
    Result.OnClick := @OnChangeItem;
f754e5
    p.InsertControl(Result);
f754e5
    r := Result.Left;
f754e5
  end;
f754e5
f754e5
  function createSquareButton(name, title: string): TButton;
f754e5
  begin
f754e5
    Result := createButton(name, title);
f754e5
    Result.Left := Result.Left + Result.Width - Result.Height;
f754e5
    Result.Width := Result.Height;
f754e5
    r := Result.Left;
f754e5
  end;
f754e5
f754e5
  function createEdit(name, title: string): TEdit;
f754e5
  begin
f754e5
    Result := TEdit.Create(p);
f754e5
    Result.Name := name;
f754e5
    Result.Hint := Title;
f754e5
    Result.ShowHint := True;
f754e5
    Result.AutoSize := False;
f754e5
    Result.BorderStyle := bsNone;
f754e5
    Result.ParentFont := True;
f754e5
    Result.ParentColor := True;
f754e5
    Result.Width := Result.Height*2;
f754e5
    Result.NumbersOnly := True;
f754e5
    Result.Top := bb;
f754e5
    Result.Left := r - Result.Width;
f754e5
    Result.Anchors := [akTop, akRight];
f754e5
    Result.OnEditingDone := @OnChangeItem;
f754e5
    p.InsertControl(Result);
f754e5
    r := Result.Left;
f754e5
  end;
f754e5
f754e5
  function createNameEdit(name, title: string): TEdit;
f754e5
  begin
f754e5
    Result := createEdit(name, title);
f754e5
    Result.NumbersOnly := False;
f754e5
    Result.Left := l;
f754e5
    Result.Width := Result.Width + r - l;
f754e5
    Result.Anchors := [akLeft, akTop, akRight];
f754e5
  end;
f754e5
f754e5
begin
f754e5
  scroll := sbTodo.VertScrollBar.Position;
8a9c8e
  plannedOnly := cbPlanned.Checked;
f754e5
f754e5
  ClearTodoList();
f754e5
f754e5
  lDate.Caption := FormatDateTime('YYYY-MM-DD', todoList.date);
f754e5
f754e5
  t := b;
f754e5
  for i := 0 to Length(todoList.items)-1 do begin
8a9c8e
    if plannedOnly and not isPlanned(todoList.items[i]) then
f754e5
      Continue;
f754e5
f754e5
    p := TPanel.Create(sbTodo);
f754e5
    p.Tag := i;
f754e5
    p.Left := b;
f754e5
    p.Width := sbTodo.Width - b*2 - sb;
f754e5
    p.Top := t;
f754e5
    p.Anchors := [akLeft, akTop, akRight];
f754e5
    p.ParentFont := False;
f754e5
    sbTodo.InsertControl(p);
f754e5
f754e5
    l := bb;
f754e5
f754e5
    createCheckBox('done', 'Done');
f754e5
    createCheckBox('doneByOthers', 'Done by others');
f754e5
    createCheckBox('cancelled', 'Cancelled');
8a9c8e
    ll := l;
8a9c8e
    Inc(l, bb);
8a9c8e
    Inc(l, bb);
f754e5
    Inc(l, bb);
8a9c8e
    createCheckBox('planned', 'Planned manually').Visible := not plannedOnly;
8a9c8e
    createCheckBox('notPlanned', 'Remove from plan manually').Visible := not plannedOnly;
8a9c8e
    if plannedOnly then l := ll;
f754e5
    Inc(l, bb);
f754e5
f754e5
    r := p.ClientWidth - bb;
f754e5
f754e5
    createButton('delete', 'delete');
f754e5
    Dec(r, bb);
f754e5
    createSquareButton('down', '+');
f754e5
    createSquareButton('up', '-');
f754e5
    Dec(r, bb);
f754e5
f754e5
    createEdit('daysSinceDone', 'Days since last done');
f754e5
    Dec(r, bb);
f754e5
    createEdit('repeatDays', 'Repeat every N days');
f754e5
    Dec(r, bb);
f754e5
    createNameEdit('name', 'Task name');
f754e5
f754e5
    h := 0;
f754e5
    for j := 0 to p.ControlCount-1 do
f754e5
      if h < p.Controls[j].Top + p.Controls[j].Height then
f754e5
        h := p.Controls[j].Top + p.Controls[j].Height;
f754e5
    p.Height := h + bb;
f754e5
    Inc(t, p.Height + b);
f754e5
f754e5
    todoList.items[i].panel := p;
f754e5
    UpdateItem(todoList.items[i]);
f754e5
  end;
f754e5
  sbTodo.UpdateScrollbars;
f754e5
f754e5
  sbTodo.ScrollBy(0, -scroll);
f754e5
end;
f754e5
f754e5
f754e5
end.
f754e5