{-----------------------------------------------------------------------------
 Unit Name: ucUndoRedo
 Author:    Fernand Veilleux
 e-mail:    fernand.veilleux-at-mr-gas-tank.com
 Date:      23-janv.-2010
 Purpose:   Manage Undos and Redos for ImageScrollBox and anything that can be
            saved in TMemoryStream

 History:

 Usage:     After create you may change the default MaxUndo

            Always Reset before loading a new graphic

            Before applying your changes :
                  ms := TMemoryStream.Create;
                  Graphic.SaveToStream(ms);
                  UndoRedo.Save(ms);      // Add ms on top of undo list DO NOT FREE ms

            CanUndo and CanRedo are available to enable/disable actions/buttons

            to Undo follow this order :

              Graphic.SaveToStream(SavedStream);
              UndoStream := UndoRedo.Prior;   // Prior returns the last savedstream
              Graphic.LoadFromStream(UndoStream);
              UndoRedo.Undone(SavedStream); // will put SavedStream on top of redo list
                                            // and delete the Prior item
                                            // DO NOT FREE YOURSELF

            to Redo follow this order :

              Graphic.SaveToStream(SavedStream);
              RedoStream := UndoRedo.Next;
              Graphic.LoadFromStream(RedoStream);
              UndoRedo.Redone(SavedStream); // will put SavedStream on top of undo list
                                            // and delete the Next item
                                            // DO NOT FREE YOURSELF

-----------------------------------------------------------------------------}

unit ucUndoRedo;

interface

uses Classes, SysUtils;

type
  EUndoRedoException = class(Exception);

  TMax_Undo = 0 .. 100;

  TUndoRedoLists = class(TObject)
  private
    FUndoList: TList;
    FRedoList: TList;
    FMaxUndo: TMax_Undo;
    procedure ClearUndoList;
    procedure ClearRedoList;
    function GetCanRedo: boolean;
    function GetCanUndo: Boolean;
    function GetNext: TMemoryStream;
    function GetPrior: TMemoryStream;
  public
    constructor Create;
    destructor Destroy; override;
    property CanUndo: boolean read GetCanUndo;
    property CanRedo: boolean read GetCanRedo;
    property Prior: TMemoryStream read GetPrior;
    property Next: TMemoryStream read GetNext;
    procedure ReDone(Archive: TMemoryStream);
    procedure UnDone(Archive: TMemoryStream);
    procedure Reset;
    property MaxUndo: TMax_Undo read FMaxUndo write FMaxUndo default 20;
    procedure Save(Stream: TMemoryStream);
  end;

implementation

resourcestring
  UndoListEmpty = 'Undo list is empty';
  RedoListEmpty = 'Redo list is empty';

  { TUndoRedoLists }

procedure TUndoRedoLists.ClearRedoList;
var
  i: integer;
begin
  for i := FRedoList.Count - 1 downto 0 do
    TMemoryStream(FRedoList.Items[i]).Free;
  FRedoList.Clear;
end;

procedure TUndoRedoLists.ClearUndoList;
var
  i: integer;
begin
  for i := FUndoList.Count - 1 downto 0 do
    TMemoryStream(FUndoList.Items[i]).Free;
  FUndoList.Clear;
end;

constructor TUndoRedoLists.Create;
begin
  FUndoList := TList.Create;
  FRedoList := TList.Create;
  FMaxUndo := 20;
end;

destructor TUndoRedoLists.Destroy;
begin
  Reset;
  FUndoList.Free;
  FRedoList.Free;
  inherited;
end;

function TUndoRedoLists.GetCanRedo: boolean;
begin
  Result := FRedoList.Count > 0;
end;

function TUndoRedoLists.GetCanUndo: boolean;
begin
  Result := FUndoList.Count > 0;
end;

/// Summary:  Returns the latest saved stream on top of redo list
function TUndoRedoLists.GetNext: TMemoryStream;
begin
  if CanRedo then
    Result := TMemoryStream(FRedoList.Last)
  else
    raise EUndoRedoException.Create(UndoListEmpty);
end;

/// Summary:  Returns the latest saved stream on top of undo list
function TUndoRedoLists.GetPrior: TMemoryStream;
begin
  if CanUndo then
    Result := TMemoryStream(FUndoList.Last)
  else
    raise EUndoRedoException.Create(RedoListEmpty);
end;

/// Summary: Save the old graphic you replaced by the Next
///
/// Parameters: Archive  Graphic saved in this stream
procedure TUndoRedoLists.ReDone(Archive: TMemoryStream);
begin
  if Archive.Size > 0 then
  begin
    Archive.Position := 0;
    FUndoList.Add(Archive);
  end
  else
    Archive.Free;
  TMemoryStream(FRedoList[FRedoList.Count - 1]).Free;
  FRedoList.Delete(FRedoList.Count - 1);
end;

/// Summary:  Clears Undo and Redo lists. Call before reloading a new graphic
procedure TUndoRedoLists.Reset;
begin
  ClearUndoList;
  ClearRedoList;
end;

/// Summary:  Save the Stream on top of the undo list
procedure TUndoRedoLists.Save(Stream: TMemoryStream);
var
  i: Integer;
begin
  if Stream.Size = 0 then // do not add empty stream
  begin
    Stream.Free;
    Exit;
  end;

  Stream.Position := 0;
  FUndoList.Add(Stream);

  // Limit to MaxUndo
  for i := FUndoList.Count - 1 downto MaxUndo do
  begin
    TMemoryStream(FUndoList.Items[0]).Free;
    FUndoList.Delete(0);
  end;

  ClearRedoList;
end;

/// Summary: Save the old graphic you replaced by the Prior
///
/// Parameters: Archive  Graphic saved in this stream
procedure TUndoRedoLists.Undone(Archive: TMemoryStream);
begin
  if Archive.Size > 0 then
  begin
    Archive.Position := 0;
    FRedoList.Add(Archive);
  end
  else
    Archive.Free;
  TMemoryStream(FUndoList[FUndoList.Count - 1]).Free;
  FUndoList.Delete(FUndoList.Count - 1);
end;

end.
