Ver Fonte

undo/redo for vector original and in vectoredit

Johann há 6 anos atrás
pai
commit
e1163c860f

+ 3 - 2
lazpaint/tools/utoolbasic.pas

@@ -105,7 +105,7 @@ type
     function DoToolDown({%H-}toolDest: TBGRABitmap; {%H-}pt: TPoint; {%H-}ptF: TPointF; rightBtn: boolean): TRect; override;
     function DoToolMove({%H-}toolDest: TBGRABitmap; {%H-}pt: TPoint; {%H-}ptF: TPointF): TRect; override;
     function DoToolUpdate({%H-}toolDest: TBGRABitmap): TRect; override;
-    procedure ShapeChange({%H-}ASender: TObject; ABounds: TRectF); virtual;
+    procedure ShapeChange({%H-}ASender: TObject; ABounds: TRectF; ADiff: TVectorShapeDiff); virtual;
     procedure ShapeEditingChange({%H-}ASender: TObject); virtual;
     function GetStatusText: string; override;
     function SlowShape: boolean; virtual;
@@ -148,7 +148,7 @@ uses Types, Graphics, ugraph, Controls, LazPaintType,
 
 { TVectorialTool }
 
-procedure TVectorialTool.ShapeChange(ASender: TObject; ABounds: TRectF);
+procedure TVectorialTool.ShapeChange(ASender: TObject; ABounds: TRectF; ADiff: TVectorShapeDiff);
 var
   toolDest: TBGRABitmap;
   r: TRect;
@@ -162,6 +162,7 @@ begin
   with FShape.GetRenderBounds(rect(0,0,toolDest.Width,toolDest.Height),matrix,[]) do
     FPreviousUpdateBounds := rect(floor(Left),floor(Top),ceil(Right),ceil(Bottom));
   if IsRectEmpty(r) then ShapeEditingChange(ASender);
+  ADiff.Free;
 end;
 
 procedure TVectorialTool.ShapeEditingChange(ASender: TObject);

+ 3 - 3
lazpaint/tools/utoolphong.pas

@@ -14,7 +14,7 @@ type
   TToolPhong = class(TVectorialTool)
   protected
     FMatrix: TAffineMatrix;
-    procedure ShapeChange({%H-}ASender: TObject; ABounds: TRectF); override;
+    procedure ShapeChange({%H-}ASender: TObject; ABounds: TRectF; ADiff: TVectorShapeDiff); override;
     procedure AssignShapeStyle(AMatrix: TAffineMatrix); override;
     function CreateShape: TVectorShape; override;
     function SlowShape: boolean; override;
@@ -34,13 +34,13 @@ begin
   FMatrix := AffineMatrixIdentity;
 end;
 
-procedure TToolPhong.ShapeChange(ASender: TObject; ABounds: TRectF);
+procedure TToolPhong.ShapeChange(ASender: TObject; ABounds: TRectF; ADiff: TVectorShapeDiff);
 var
   posF: TPointF;
 begin
   posF := AffineMatrixInverse(FMatrix)*(FShape as TPhongShape).LightPosition;
   Manager.ToolLightPosition := posF;
-  inherited ShapeChange(ASender, ABounds);
+  inherited ShapeChange(ASender, ABounds, ADiff);
 end;
 
 procedure TToolPhong.AssignShapeStyle(AMatrix: TAffineMatrix);

+ 3 - 3
lazpaint/tools/utooltext.pas

@@ -23,7 +23,7 @@ type
     procedure IncludeShadowBounds(var ARect: TRect);
     function GetCustomShapeBounds(ADestBounds: TRect; AMatrix: TAffineMatrix; ADraft: boolean): TRect; override;
     procedure DrawCustomShape(ADest: TBGRABitmap; AMatrix: TAffineMatrix; ADraft: boolean); override;
-    procedure ShapeChange(ASender: TObject; ABounds: TRectF); override;
+    procedure ShapeChange(ASender: TObject; ABounds: TRectF; ADiff: TVectorShapeDiff); override;
     procedure ShapeEditingChange(ASender: TObject); override;
     procedure AssignShapeStyle(AMatrix: TAffineMatrix); override;
     function SlowShape: boolean; override;
@@ -126,7 +126,7 @@ begin
   end;
 end;
 
-procedure TToolText.ShapeChange(ASender: TObject; ABounds: TRectF);
+procedure TToolText.ShapeChange(ASender: TObject; ABounds: TRectF; ADiff: TVectorShapeDiff);
 var
   r: TRect;
   posF: TPointF;
@@ -135,7 +135,7 @@ begin
   Manager.ToolLightPosition := posF;
   with ABounds do r := rect(floor(Left),floor(Top),ceil(Right),ceil(Bottom));
   IncludeShadowBounds(r);
-  inherited ShapeChange(ASender, RectF(r.Left,r.Top,r.Right,r.Bottom));
+  inherited ShapeChange(ASender, RectF(r.Left,r.Top,r.Right,r.Bottom), ADiff);
 end;
 
 procedure TToolText.ShapeEditingChange(ASender: TObject);

+ 229 - 26
lazpaintcontrols/lcvectorialfill.pas

@@ -11,6 +11,48 @@ uses
 type
   TTextureRepetition = (trNone, trRepeatX, trRepeatY, trRepeatBoth);
   TVectorialFillType = (vftNone, vftSolid, vftGradient, vftTexture);
+  TVectorialFill = class;
+
+  TCustomVectorialFillDiff = class
+    procedure Apply(AFill: TVectorialFill); virtual; abstract;
+    procedure Unapply(AFill: TVectorialFill); virtual; abstract;
+    function IsIdentity: boolean; virtual; abstract;
+    function CanAppend(ADiff: TCustomVectorialFillDiff): boolean; virtual; abstract;
+    procedure Append(ADiff: TCustomVectorialFillDiff); virtual; abstract;
+  end;
+
+  TVectorialFillChangeEvent = procedure(ASender: TObject; var ADiff: TCustomVectorialFillDiff) of object;
+
+  { TVectorialFillGradientDiff }
+
+  TVectorialFillGradientDiff = class(TCustomVectorialFillDiff)
+  protected
+    FGradientDiff: TBGRAGradientOriginalDiff;
+  public
+    constructor Create(AGradientDiff: TBGRAGradientOriginalDiff);
+    destructor Destroy; override;
+    procedure Apply(AFill: TVectorialFill); override;
+    procedure Unapply(AFill: TVectorialFill); override;
+    function IsIdentity: boolean; override;
+    function CanAppend(ADiff: TCustomVectorialFillDiff): boolean; override;
+    procedure Append(ADiff: TCustomVectorialFillDiff); override;
+  end;
+
+  { TVectorialFillDiff }
+
+  TVectorialFillDiff = class(TCustomVectorialFillDiff)
+  protected
+    FStart,FEnd: TVectorialFill;
+  public
+    constructor Create(AFrom: TVectorialFill);
+    procedure ComputeDiff(ATo: TVectorialFill);
+    destructor Destroy; override;
+    procedure Apply(AFill: TVectorialFill); override;
+    procedure Unapply(AFill: TVectorialFill); override;
+    function IsIdentity: boolean; override;
+    function CanAppend(ADiff: TCustomVectorialFillDiff): boolean; override;
+    procedure Append(ADiff: TCustomVectorialFillDiff); override;
+  end;
 
   { TVectorialFill }
 
@@ -24,17 +66,21 @@ type
     FTextureOpacity: byte;
     FTextureRepetition: TTextureRepetition;
     FGradient: TBGRALayerGradientOriginal;
-    FOnChange: TNotifyEvent;
-    procedure GradientChange({%H-}ASender: TObject; {%H-}ABounds: PRectF=nil);
+    FOnChange: TVectorialFillChangeEvent;
+    FOnBeforeChange: TNotifyEvent;
+    FDiff: TVectorialFillDiff;
+    procedure GradientChange({%H-}ASender: TObject; {%H-}ABounds: PRectF; var ADiff: TBGRAOriginalDiff);
     procedure Init; virtual;
     function GetFillType: TVectorialFillType;
     function GetIsEditable: boolean;
-    procedure SetOnChange(AValue: TNotifyEvent);
+    procedure SetOnChange(AValue: TVectorialFillChangeEvent);
     procedure SetTextureMatrix(AValue: TAffineMatrix);
     procedure SetTextureOpacity(AValue: byte);
     procedure SetTextureRepetition(AValue: TTextureRepetition);
     procedure InternalClear;
-    procedure Changed;
+    procedure BeginUpdate;
+    procedure EndUpdate;
+    procedure NotifyChangeWithoutDiff;
     procedure ConfigureTextureEditor(AEditor: TBGRAOriginalEditor);
     procedure TextureMoveOrigin({%H-}ASender: TObject; {%H-}APrevCoord,
       ANewCoord: TPointF; {%H-}AShift: TShiftState);
@@ -72,16 +118,121 @@ type
     property TextureMatrix: TAffineMatrix read FTextureMatrix write SetTextureMatrix;
     property TextureOpacity: byte read FTextureOpacity write SetTextureOpacity;
     property TextureRepetition: TTextureRepetition read FTextureRepetition write SetTextureRepetition;
-    property OnChange: TNotifyEvent read FOnChange write SetOnChange;
+    property OnChange: TVectorialFillChangeEvent read FOnChange write SetOnChange;
+    property OnBeforeChange: TNotifyEvent read FOnBeforeChange write FOnBeforeChange;
   end;
 
 implementation
 
 uses BGRAGradientScanner;
 
+{ TVectorialFillDiff }
+
+constructor TVectorialFillDiff.Create(AFrom: TVectorialFill);
+begin
+  FStart := TVectorialFill.Create;
+  FStart.Assign(AFrom);
+end;
+
+procedure TVectorialFillDiff.ComputeDiff(ATo: TVectorialFill);
+begin
+  FEnd := TVectorialFill.Create;
+  FEnd.Assign(ATo);
+end;
+
+destructor TVectorialFillDiff.Destroy;
+begin
+  FStart.Free;
+  FEnd.Free;
+  inherited Destroy;
+end;
+
+procedure TVectorialFillDiff.Apply(AFill: TVectorialFill);
+var
+  oldChange: TVectorialFillChangeEvent;
+begin
+  oldChange := AFill.OnChange;
+  AFill.OnChange := nil;
+  AFill.Assign(FEnd);
+  AFill.OnChange := oldChange;
+  AFill.NotifyChangeWithoutDiff;
+end;
+
+procedure TVectorialFillDiff.Unapply(AFill: TVectorialFill);
+var
+  oldChange: TVectorialFillChangeEvent;
+begin
+  oldChange := AFill.OnChange;
+  AFill.OnChange := nil;
+  AFill.Assign(FStart);
+  AFill.OnChange := oldChange;
+  AFill.NotifyChangeWithoutDiff;
+end;
+
+function TVectorialFillDiff.IsIdentity: boolean;
+begin
+  result := FStart.Equals(FEnd);
+end;
+
+function TVectorialFillDiff.CanAppend(ADiff: TCustomVectorialFillDiff
+  ): boolean;
+begin
+  result := ADiff is TVectorialFillDiff;
+end;
+
+procedure TVectorialFillDiff.Append(ADiff: TCustomVectorialFillDiff);
+begin
+  FEnd.Assign((ADiff as TVectorialFillDiff).FEnd);
+end;
+
+{ TVectorialFillGradientDiff }
+
+constructor TVectorialFillGradientDiff.Create(
+  AGradientDiff: TBGRAGradientOriginalDiff);
+begin
+  FGradientDiff := AGradientDiff;
+end;
+
+destructor TVectorialFillGradientDiff.Destroy;
+begin
+  FGradientDiff.Free;
+  inherited Destroy;
+end;
+
+procedure TVectorialFillGradientDiff.Apply(AFill: TVectorialFill);
+begin
+  if AFill.FillType = vftGradient then
+    FGradientDiff.Apply(AFill.Gradient);
+end;
+
+procedure TVectorialFillGradientDiff.Unapply(AFill: TVectorialFill);
+begin
+  if AFill.FillType = vftGradient then
+    FGradientDiff.Unapply(AFill.Gradient);
+end;
+
+function TVectorialFillGradientDiff.IsIdentity: boolean;
+begin
+  result := false;
+end;
+
+function TVectorialFillGradientDiff.CanAppend(ADiff: TCustomVectorialFillDiff): boolean;
+begin
+  result := (ADiff is TVectorialFillGradientDiff) and
+    FGradientDiff.CanAppend(TVectorialFillGradientDiff(ADiff).FGradientDiff);
+end;
+
+procedure TVectorialFillGradientDiff.Append(ADiff: TCustomVectorialFillDiff);
+var
+  nextDiff: TVectorialFillGradientDiff;
+begin
+  nextDiff := ADiff as TVectorialFillGradientDiff;
+  FGradientDiff.Append(nextDiff.FGradientDiff);
+end;
+
 { TVectorialFill }
 
-procedure TVectorialFill.SetOnChange(AValue: TNotifyEvent);
+procedure TVectorialFill.SetOnChange(AValue: TVectorialFillChangeEvent);
 begin
   if FOnChange=AValue then Exit;
   FOnChange:=AValue;
@@ -91,16 +242,18 @@ procedure TVectorialFill.SetTextureMatrix(AValue: TAffineMatrix);
 begin
   if FillType <> vftTexture then raise exception.Create('Not a texture fill');
   if FTextureMatrix=AValue then Exit;
+  BeginUpdate;
   FTextureMatrix:=AValue;
-  Changed;
+  EndUpdate;
 end;
 
 procedure TVectorialFill.SetTextureOpacity(AValue: byte);
 begin
   if FillType <> vftTexture then raise exception.Create('Not a texture fill');
   if FTextureOpacity=AValue then Exit;
+  BeginUpdate;
   FTextureOpacity:=AValue;
-  Changed;
+  EndUpdate;
 end;
 
 procedure TVectorialFill.InternalClear;
@@ -121,9 +274,37 @@ begin
   FTextureRepetition:= trRepeatBoth;
 end;
 
-procedure TVectorialFill.Changed;
+procedure TVectorialFill.BeginUpdate;
 begin
-  if Assigned(OnChange) then OnChange(self);
+  if Assigned(OnBeforeChange) then
+    OnBeforeChange(self);
+  if Assigned(OnChange) and (FDiff = nil) then
+    FDiff := TVectorialFillDiff.Create(self);
+end;
+
+procedure TVectorialFill.EndUpdate;
+begin
+  if Assigned(OnChange) then
+  begin
+    if Assigned(FDiff) then
+    begin
+      FDiff.ComputeDiff(self);
+      if not FDiff.IsIdentity then OnChange(self, FDiff);
+    end
+    else
+      OnChange(self, FDiff);
+  end;
+  FreeAndNil(FDiff);
+end;
+
+procedure TVectorialFill.NotifyChangeWithoutDiff;
+var diff: TCustomVectorialFillDiff;
+begin
+  if Assigned(FOnChange) then
+  begin
+    diff := nil;
+    FOnChange(self, diff);
+  end;
 end;
 
 procedure TVectorialFill.ConfigureTextureEditor(AEditor: TBGRAOriginalEditor);
@@ -147,9 +328,10 @@ end;
 procedure TVectorialFill.TextureMoveOrigin(ASender: TObject; APrevCoord,
   ANewCoord: TPointF; AShift: TShiftState);
 begin
+  BeginUpdate;
   FTextureMatrix[1,3] := ANewCoord.x;
   FTextureMatrix[2,3] := ANewCoord.y;
-  Changed;
+  EndUpdate;
 end;
 
 procedure TVectorialFill.TextureMoveXAxis(ASender: TObject; APrevCoord,
@@ -157,6 +339,7 @@ procedure TVectorialFill.TextureMoveXAxis(ASender: TObject; APrevCoord,
 var
   origin, xAxisRel: TPointF;
 begin
+  BeginUpdate;
   FTextureMatrix := FTextureMatrixBackup;
   origin := PointF(FTextureMatrix[1,3],FTextureMatrix[2,3]);
   xAxisRel := (ANewCoord - origin)*(1/FTexture.Width);
@@ -169,7 +352,7 @@ begin
     FTextureMatrix := AffineMatrixTranslation(origin.x,origin.y)*
                      AffineMatrixScaledRotation(PointF(FTextureMatrix[1,1],FTextureMatrix[2,1]), xAxisRel)*
                      AffineMatrixLinear(FTextureMatrix);
-  Changed;
+  EndUpdate;
 end;
 
 procedure TVectorialFill.TextureMoveYAxis(ASender: TObject; APrevCoord,
@@ -177,6 +360,7 @@ procedure TVectorialFill.TextureMoveYAxis(ASender: TObject; APrevCoord,
 var
   origin, yAxisRel: TPointF;
 begin
+  BeginUpdate;
   FTextureMatrix := FTextureMatrixBackup;
   origin := PointF(FTextureMatrix[1,3],FTextureMatrix[2,3]);
   yAxisRel := (ANewCoord - origin)*(1/FTexture.Height);
@@ -189,7 +373,7 @@ begin
     FTextureMatrix := AffineMatrixTranslation(origin.x,origin.y)*
                      AffineMatrixScaledRotation(PointF(FTextureMatrix[1,2],FTextureMatrix[2,2]), yAxisRel)*
                      AffineMatrixLinear(FTextureMatrix);
-  Changed;
+  EndUpdate;
 end;
 
 procedure TVectorialFill.TextureStartMove(ASender: TObject; AIndex: integer;
@@ -217,8 +401,9 @@ procedure TVectorialFill.SetTextureRepetition(AValue: TTextureRepetition);
 begin
   if FillType <> vftTexture then raise exception.Create('Not a texture fill');
   if FTextureRepetition=AValue then Exit;
+  BeginUpdate;
   FTextureRepetition:=AValue;
-  Changed;
+  EndUpdate;
 end;
 
 function TVectorialFill.GetFillType: TVectorialFillType;
@@ -229,9 +414,21 @@ begin
   else result := vftNone;
 end;
 
-procedure TVectorialFill.GradientChange(ASender: TObject; ABounds: PRectF=nil);
+procedure TVectorialFill.GradientChange(ASender: TObject; ABounds: PRectF; var ADiff: TBGRAOriginalDiff);
+var
+  fillDiff: TVectorialFillGradientDiff;
 begin
-  Changed;
+  if Assigned(OnChange) then
+  begin
+    if Assigned(FDiff) then
+    begin
+      fillDiff := TVectorialFillGradientDiff.Create(ADiff as TBGRAGradientOriginalDiff);
+      ADiff := nil;
+    end else
+      fillDiff := nil;
+    FOnChange(self, fillDiff);
+    fillDiff.Free;
+  end;
 end;
 
 constructor TVectorialFill.Create;
@@ -240,12 +437,14 @@ begin
 end;
 
 procedure TVectorialFill.Clear;
-var
-  notify: Boolean;
 begin
-  notify := FillType <> vftNone;
-  InternalClear;
-  if notify then Changed;
+  if FillType <> vftNone then
+  begin
+    BeginUpdate;
+    InternalClear;
+    EndUpdate;
+  end else
+    InternalClear;
 end;
 
 constructor TVectorialFill.CreateAsSolid(AColor: TBGRAPixel);
@@ -271,32 +470,35 @@ end;
 procedure TVectorialFill.SetSolid(AColor: TBGRAPixel);
 begin
   if (FillType = vftSolid) and (SolidColor = AColor) then exit;
+  BeginUpdate;
   InternalClear;
   if AColor.alpha = 0 then AColor := BGRAPixelTransparent;
   FColor := AColor;
   FIsSolid:= true;
-  Changed;
+  EndUpdate;
 end;
 
 procedure TVectorialFill.SetTexture(ATexture: TBGRABitmap;
   AMatrix: TAffineMatrix; AOpacity: byte; ATextureRepetition: TTextureRepetition);
 begin
+  BeginUpdate;
   InternalClear;
   FTexture := ATexture.NewReference as TBGRABitmap;
   FTextureMatrix := AMatrix;
   FTextureOpacity:= AOpacity;
   FTextureRepetition:= ATextureRepetition;
-  Changed;
+  EndUpdate;
 end;
 
 procedure TVectorialFill.SetGradient(AGradient: TBGRALayerGradientOriginal;
   AOwned: boolean);
 begin
+  BeginUpdate;
   InternalClear;
   if AOwned then FGradient := AGradient
   else FGradient := AGradient.Duplicate as TBGRALayerGradientOriginal;
-  FGradient.OnChange:=@GradientChange;
-  Changed;
+  FGradient.OnChange:= @GradientChange;
+  EndUpdate;
 end;
 
 procedure TVectorialFill.ConfigureEditor(AEditor: TBGRAOriginalEditor);
@@ -363,8 +565,9 @@ begin
   vftGradient: Gradient.Transform(AMatrix);
   vftTexture:
     begin
+      BeginUpdate;
       FTextureMatrix := AMatrix*FTextureMatrix;
-      Changed;
+      EndUpdate;
     end;
   end;
 end;

Diff do ficheiro suprimidas por serem muito extensas
+ 739 - 35
lazpaintcontrols/lcvectororiginal.pas


+ 198 - 48
lazpaintcontrols/lcvectorpolyshapes.pas

@@ -22,6 +22,33 @@ const
 function StrToArrowKind(AStr: string): TArrowKind;
 
 type
+  TCustomPolypointShape = class;
+  TCustomPolypointPoint = record
+    coord: TPointF;
+    editorIndex: integer;
+    data: cardinal;
+  end;
+
+  { TCustomPolypointShapeDiff }
+
+  TCustomPolypointShapeDiff = class(TVectorShapeDiff)
+  protected
+    FStartPoints: array of TCustomPolypointPoint;
+    FStartClosed: boolean;
+    FStartArrowStartKind,FStartArrowEndKind: TArrowKind;
+    FStartArrowSize: TPointF;
+    FEndPoints: array of TCustomPolypointPoint;
+    FEndClosed: boolean;
+    FEndArrowStartKind,FEndArrowEndKind: TArrowKind;
+    FEndArrowSize: TPointF;
+  public
+    constructor Create(AStartShape: TVectorShape); override;
+    procedure ComputeDiff(AEndShape: TVectorShape); override;
+    procedure Apply(AStartShape: TVectorShape); override;
+    procedure Unapply(AEndShape: TVectorShape); override;
+    procedure Append(ADiff: TVectorShapeDiff); override;
+  end;
+
   { TCustomPolypointShape }
 
   TCustomPolypointShape = class(TVectorShape)
@@ -34,11 +61,7 @@ type
     procedure SetArrowStartKind(AValue: TArrowKind);
     procedure SetPoint(AIndex: integer; AValue: TPointF);
   protected
-    FPoints: array of record
-               coord: TPointF;
-               editorIndex: integer;
-               data: pointer;
-             end;
+    FPoints: array of TCustomPolypointPoint;
     FCenterPoint: TPointF;
     FCenterPointEditorIndex: integer;
     FCurPoint: integer;
@@ -65,7 +88,6 @@ type
   public
     constructor Create(AContainer: TVectorOriginal); override;
     procedure Clear;
-    destructor Destroy; override;
     function AddPoint(const APoint: TPointF): integer; virtual;
     function RemovePoint(AIndex: integer): boolean;
     procedure RemovePointRange(AFromIndex, AToIndexPlus1: integer);
@@ -104,6 +126,24 @@ type
     class function StorageClassName: RawByteString; override;
   end;
 
+  TCurveShape = class;
+
+  { TCurveShapeDiff }
+
+  TCurveShapeDiff = class(TVectorShapeDiff)
+  protected
+    FStartCosineAngle: single;
+    FStartSplineStyle: TSplineStyle;
+    FEndCosineAngle: single;
+    FEndSplineStyle: TSplineStyle;
+  public
+    constructor Create(AStartShape: TVectorShape); override;
+    procedure ComputeDiff(AEndShape: TVectorShape); override;
+    procedure Apply(AStartShape: TVectorShape); override;
+    procedure Unapply(AEndShape: TVectorShape); override;
+    procedure Append(ADiff: TVectorShapeDiff); override;
+  end;
+
   { TCurveShape }
 
   TCurveShape = class(TPolylineShape)
@@ -199,6 +239,137 @@ begin
     end;
 end;
 
+{ TCurveShapeDiff }
+
+constructor TCurveShapeDiff.Create(AStartShape: TVectorShape);
+begin
+  with (AStartShape as TCurveShape) do
+  begin
+    FStartCosineAngle:= FCosineAngle;
+    FStartSplineStyle:= FSplineStyle;
+  end;
+end;
+
+procedure TCurveShapeDiff.ComputeDiff(AEndShape: TVectorShape);
+begin
+  with (AEndShape as TCurveShape) do
+  begin
+    FEndCosineAngle:= FCosineAngle;
+    FEndSplineStyle:= FSplineStyle;
+  end;
+end;
+
+procedure TCurveShapeDiff.Apply(AStartShape: TVectorShape);
+begin
+  with (AStartShape as TCurveShape) do
+  begin
+    BeginUpdate;
+    FCosineAngle := FEndCosineAngle;
+    FSplineStyle := FEndSplineStyle;
+    EndUpdate;
+  end;
+end;
+
+procedure TCurveShapeDiff.Unapply(AEndShape: TVectorShape);
+begin
+  with (AEndShape as TCurveShape) do
+  begin
+    BeginUpdate;
+    FCosineAngle := FStartCosineAngle;
+    FSplineStyle := FStartSplineStyle;
+    EndUpdate;
+  end;
+end;
+
+procedure TCurveShapeDiff.Append(ADiff: TVectorShapeDiff);
+var
+  next: TCurveShapeDiff;
+begin
+  next := ADiff as TCurveShapeDiff;
+  FEndCosineAngle:= next.FEndCosineAngle;
+  FEndSplineStyle:= next.FEndSplineStyle;
+end;
+
+{ TCustomPolypointShapeDiff }
+
+constructor TCustomPolypointShapeDiff.Create(AStartShape: TVectorShape);
+var
+  i: Integer;
+begin
+  with (AStartShape as TCustomPolypointShape) do
+  begin
+    setlength(FStartPoints, length(FPoints));
+    for i := 0 to high(FPoints) do FStartPoints[i] := FPoints[i];
+    FStartClosed:= FClosed;
+    FStartArrowStartKind := FArrowStartKind;
+    FStartArrowEndKind:= FArrowEndKind;
+    FStartArrowSize:= FArrowSize;
+  end;
+end;
+
+procedure TCustomPolypointShapeDiff.ComputeDiff(AEndShape: TVectorShape);
+var
+  i: Integer;
+begin
+  with (AEndShape as TCustomPolypointShape) do
+  begin
+    setlength(FEndPoints, length(FPoints));
+    for i := 0 to high(FPoints) do FEndPoints[i] := FPoints[i];
+    FEndClosed:= FClosed;
+    FEndArrowStartKind := FArrowStartKind;
+    FEndArrowEndKind:= FArrowEndKind;
+    FEndArrowSize:= FArrowSize;
+  end;
+end;
+
+procedure TCustomPolypointShapeDiff.Apply(AStartShape: TVectorShape);
+var
+  i: Integer;
+begin
+  with (AStartShape as TCustomPolypointShape) do
+  begin
+    BeginUpdate;
+    setlength(FPoints, length(FEndPoints));
+    for i := 0 to high(FPoints) do FPoints[i] := FEndPoints[i];
+    FClosed := FEndClosed;
+    FArrowStartKind := FEndArrowStartKind;
+    FArrowEndKind := FEndArrowEndKind;
+    FArrowSize := FEndArrowSize;
+    EndUpdate;
+  end;
+end;
+
+procedure TCustomPolypointShapeDiff.Unapply(AEndShape: TVectorShape);
+var
+  i: Integer;
+begin
+  with (AEndShape as TCustomPolypointShape) do
+  begin
+    BeginUpdate;
+    setlength(FPoints, length(FStartPoints));
+    for i := 0 to high(FPoints) do FPoints[i] := FStartPoints[i];
+    FClosed := FStartClosed;
+    FArrowStartKind := FStartArrowStartKind;
+    FArrowEndKind := FStartArrowEndKind;
+    FArrowSize := FStartArrowSize;
+    EndUpdate;
+  end;
+end;
+
+procedure TCustomPolypointShapeDiff.Append(ADiff: TVectorShapeDiff);
+var
+  next: TCustomPolypointShapeDiff;
+  i: Integer;
+begin
+  next := ADiff as TCustomPolypointShapeDiff;
+  setlength(FEndPoints, length(next.FEndPoints));
+  for i := 0 to high(FEndPoints) do FEndPoints[i] := next.FEndPoints[i];
+  FEndClosed := next.FEndClosed;
+  FEndArrowStartKind := next.FEndArrowStartKind;
+  FEndArrowEndKind := next.FEndArrowEndKind;
+  FEndArrowSize := next.FEndArrowSize;
+end;
+
 { TCustomPolypointShape }
 
 function TCustomPolypointShape.GetClosed: boolean;
@@ -221,7 +392,7 @@ end;
 procedure TCustomPolypointShape.SetArrowEndKind(AValue: TArrowKind);
 begin
   if FArrowEndKind=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TCustomPolypointShapeDiff);
   FArrowEndKind:=AValue;
   EndUpdate;
 end;
@@ -229,7 +400,7 @@ end;
 procedure TCustomPolypointShape.SetArrowSize(AValue: TPointF);
 begin
   if FArrowSize=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TCustomPolypointShapeDiff);
   FArrowSize:=AValue;
   EndUpdate;
 end;
@@ -237,7 +408,7 @@ end;
 procedure TCustomPolypointShape.SetArrowStartKind(AValue: TArrowKind);
 begin
   if FArrowStartKind=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TCustomPolypointShapeDiff);
   FArrowStartKind:=AValue;
   EndUpdate;
 end;
@@ -245,7 +416,7 @@ end;
 procedure TCustomPolypointShape.SetClosed(AValue: boolean);
 begin
   if AValue = FClosed then exit;
-  BeginUpdate;
+  BeginUpdate(TCustomPolypointShapeDiff);
   FClosed := AValue;
   EndUpdate;
 end;
@@ -254,13 +425,13 @@ procedure TCustomPolypointShape.SetPoint(AIndex: integer; AValue: TPointF);
 begin
   if (AIndex < 0) or (AIndex > length(FPoints)) then
     raise ERangeError.Create('Index out of bounds');
-  BeginUpdate;
+  BeginUpdate(TCustomPolypointShapeDiff);
   if AIndex = length(FPoints) then
   begin
     setlength(FPoints, length(FPoints)+1);
     FPoints[AIndex].coord := AValue;
     FPoints[AIndex].editorIndex := -1;
-    FPoints[AIndex].data := nil;
+    FPoints[AIndex].data := 0;
   end
   else
     FPoints[AIndex].coord := AValue;
@@ -271,7 +442,7 @@ procedure TCustomPolypointShape.OnMovePoint(ASender: TObject; APrevCoord,
   ANewCoord: TPointF; AShift: TShiftState);
 begin
   if FCurPoint = -1 then exit;
-  BeginUpdate;
+  BeginUpdate(TCustomPolypointShapeDiff);
   Points[FCurPoint] := ANewCoord;
   EndUpdate;
 end;
@@ -282,7 +453,7 @@ var
   i: Integer;
   delta: TPointF;
 begin
-  BeginUpdate;
+  BeginUpdate(TCustomPolypointShapeDiff);
   delta := ANewCoord - APrevCoord;
   for i := 0 to PointCount-1 do
     Points[i] := Points[i]+delta;
@@ -477,18 +648,6 @@ begin
   RemovePointRange(0, PointCount);
 end;
 
-destructor TCustomPolypointShape.Destroy;
-var
-  i: Integer;
-begin
-  for i := 0 to PointCount-1 do
-  begin
-    FreeMem(FPoints[i].data);
-    FPoints[i].data := nil;
-  end;
-  inherited Destroy;
-end;
-
 function TCustomPolypointShape.AddPoint(const APoint: TPointF): integer;
 begin
   result := PointCount;
@@ -509,12 +668,7 @@ begin
   if AFromIndex < 0 then AFromIndex:= 0;
   if AToIndexPlus1 > PointCount then AToIndexPlus1:= PointCount;
   if AFromIndex >= AToIndexPlus1 then exit;
-  BeginUpdate;
-  for i := AFromIndex to AToIndexPlus1-1 do
-  begin
-    freemem(FPoints[i].data);
-    FPoints[i].data := nil;
-  end;
+  BeginUpdate(TCustomPolypointShapeDiff);
   delCount := AToIndexPlus1-AFromIndex;
   for i := AFromIndex to PointCount-DelCount-1 do
     FPoints[i] := FPoints[i+delCount];
@@ -527,13 +681,13 @@ var
   i: Integer;
 begin
   if (AIndex < 0) or (AIndex > PointCount) then raise exception.Create('Index out of bounds');
-  BeginUpdate;
+  BeginUpdate(TCustomPolypointShapeDiff);
   setlength(FPoints, PointCount+1);
   for i := PointCount-1 downto AIndex+1 do
     FPoints[i] := FPoints[i-1];
   FPoints[AIndex].coord := APoint;
   FPoints[AIndex].editorIndex:= -1;
-  FPoints[AIndex].data := nil;
+  FPoints[AIndex].data := 0;
   EndUpdate;
 end;
 
@@ -574,7 +728,7 @@ begin
   begin
     if (FHoverPoint >= 0) and (FHoverPoint < PointCount) then
     begin
-      BeginUpdate;
+      BeginUpdate(TCustomPolypointShapeDiff);
       RemovePoint(FHoverPoint);
       if (FHoverPoint < PointCount) and IsEmptyPointF(Points[FHoverPoint]) then RemovePoint(FHoverPoint);
       EndUpdate;
@@ -598,7 +752,7 @@ end;
 
 procedure TCustomPolypointShape.QuickDefine(const APoint1, APoint2: TPointF);
 begin
-  BeginUpdate;
+  BeginUpdate(TCustomPolypointShapeDiff);
   FPoints := nil;
   AddPoint(APoint1);
   if not PointsEqual(APoint1,APoint2) then
@@ -622,7 +776,7 @@ begin
   begin
     FPoints[i].coord := PointF(x[i],y[i]);
     FPoints[i].editorIndex := -1;
-    FPoints[i].data := nil;
+    FPoints[i].data := 0;
   end;
   FClosed:= AStorage.Bool['closed'];
   if AStorage.HasAttribute('arrow-size') then
@@ -696,10 +850,10 @@ procedure TCustomPolypointShape.Transform(AMatrix: TAffineMatrix);
 var
   i: Integer;
 begin
-  BeginUpdate;
-  inherited Transform(AMatrix);
+  BeginUpdate(TCustomPolypointShapeDiff);
   for i := 0 to PointCount-1 do
     FPoints[i].coord := AMatrix*FPoints[i].coord;
+  inherited Transform(AMatrix);
   EndUpdate;
 end;
 
@@ -836,7 +990,7 @@ end;
 procedure TCurveShape.SetSplineStyle(AValue: TSplineStyle);
 begin
   if FSplineStyle=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TCurveShapeDiff);
   FSplineStyle:=AValue;
   EndUpdate;
 end;
@@ -844,16 +998,13 @@ end;
 function TCurveShape.GetCurveMode(AIndex: integer): TEasyBezierCurveMode;
 begin
   if (AIndex < 0) or (AIndex >= PointCount) then exit(cmCurve);
-  if Assigned(FPoints[AIndex].data) then
-    result := TEasyBezierCurveMode(FPoints[AIndex].data^)
-  else
-    result := cmAuto;
+  result := TEasyBezierCurveMode(FPoints[AIndex].data);
 end;
 
 procedure TCurveShape.SetCosineAngle(AValue: single);
 begin
   if FCosineAngle=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TCurveShapeDiff);
   FCosineAngle:=AValue;
   EndUpdate;
 end;
@@ -862,9 +1013,8 @@ procedure TCurveShape.SetCurveMode(AIndex: integer; AValue: TEasyBezierCurveMode
 begin
   if (AIndex < 0) or (AIndex >= PointCount) then exit;
   if CurveMode[AIndex] = AValue then exit;
-  BeginUpdate;
-  if FPoints[AIndex].data = nil then FPoints[AIndex].data := getmem(sizeof(TEasyBezierCurveMode));
-  TEasyBezierCurveMode(FPoints[AIndex].data^) := AValue;
+  BeginUpdate(TCustomPolypointShapeDiff);
+  FPoints[AIndex].data := ord(AValue);
   EndUpdate
 end;
 

+ 171 - 13
lazpaintcontrols/lcvectorrectshapes.pas

@@ -9,6 +9,24 @@ uses
   BGRABitmap, BGRATransform, BGRAGradients;
 
 type
+  TCustomRectShape = class;
+
+  { TCustomRectShapeDiff }
+
+  TCustomRectShapeDiff = class(TVectorShapeDiff)
+  protected
+    FStartOrigin, FStartXAxis, FStartYAxis: TPointF;
+    FStartFixedRatio: Single;
+    FEndOrigin, FEndXAxis, FEndYAxis: TPointF;
+    FEndFixedRatio: Single;
+  public
+    constructor Create(AStartShape: TVectorShape); override;
+    procedure ComputeDiff(AEndShape: TVectorShape); override;
+    procedure Apply(AStartShape: TVectorShape); override;
+    procedure Unapply(AEndShape: TVectorShape); override;
+    procedure Append(ADiff: TVectorShapeDiff); override;
+  end;
+
   { TCustomRectShape }
 
   TCustomRectShape = class(TVectorShape)
@@ -100,6 +118,26 @@ const
   DefaultPhongBorderSizePercent = 20;
 
 type
+  TPhongShape = class;
+
+  { TPhongShapeDiff }
+
+  TPhongShapeDiff = class(TVectorShapeDiff)
+  protected
+    FStartShapeKind: TPhongShapeKind;
+    FStartLightPosition: TPointF;
+    FStartShapeAltitudePercent,FStartBorderSizePercent: single;
+    FEndShapeKind: TPhongShapeKind;
+    FEndLightPosition: TPointF;
+    FEndShapeAltitudePercent,FEndBorderSizePercent: single;
+  public
+    constructor Create(AStartShape: TVectorShape); override;
+    procedure ComputeDiff(AEndShape: TVectorShape); override;
+    procedure Apply(AStartShape: TVectorShape); override;
+    procedure Unapply(AEndShape: TVectorShape); override;
+    procedure Append(ADiff: TVectorShapeDiff); override;
+  end;
+
   { TPhongShape }
 
   TPhongShape = class(TCustomRectShape)
@@ -143,6 +181,126 @@ implementation
 
 uses BGRAPen, BGRAGraphics, BGRAFillInfo, BGRAPath, math, LCVectorialFill;
 
+{ TPhongShapeDiff }
+
+constructor TPhongShapeDiff.Create(AStartShape: TVectorShape);
+begin
+  with (AStartShape as TPhongShape) do
+  begin
+    FStartShapeKind:= ShapeKind;
+    FStartLightPosition:= LightPosition;
+    FStartShapeAltitudePercent:= ShapeAltitudePercent;
+    FStartBorderSizePercent:= BorderSizePercent;
+  end;
+end;
+
+procedure TPhongShapeDiff.ComputeDiff(AEndShape: TVectorShape);
+begin
+  with (AEndShape as TPhongShape) do
+  begin
+    FEndShapeKind:= ShapeKind;
+    FEndLightPosition:= LightPosition;
+    FEndShapeAltitudePercent:= ShapeAltitudePercent;
+    FEndBorderSizePercent:= BorderSizePercent;
+  end;
+end;
+
+procedure TPhongShapeDiff.Apply(AStartShape: TVectorShape);
+begin
+  with (AStartShape as TPhongShape) do
+  begin
+    BeginUpdate;
+    FShapeKind := FEndShapeKind;
+    FLightPosition := FEndLightPosition;
+    FShapeAltitudePercent := FEndShapeAltitudePercent;
+    FBorderSizePercent := FEndBorderSizePercent;
+    EndUpdate;
+  end;
+end;
+
+procedure TPhongShapeDiff.Unapply(AEndShape: TVectorShape);
+begin
+  with (AEndShape as TPhongShape) do
+  begin
+    FShapeKind := FStartShapeKind;
+    FLightPosition := FStartLightPosition;
+    FShapeAltitudePercent := FStartShapeAltitudePercent;
+    FBorderSizePercent := FStartBorderSizePercent;
+  end;
+end;
+
+procedure TPhongShapeDiff.Append(ADiff: TVectorShapeDiff);
+var
+  next: TPhongShapeDiff;
+begin
+  next := ADiff as TPhongShapeDiff;
+  FEndShapeKind := next.FEndShapeKind;
+  FEndLightPosition := next.FEndLightPosition;
+  FEndShapeAltitudePercent := next.FEndShapeAltitudePercent;
+  FEndBorderSizePercent := next.FEndBorderSizePercent;
+end;
+
+{ TCustomRectShapeDiff }
+
+constructor TCustomRectShapeDiff.Create(AStartShape: TVectorShape);
+begin
+  with (AStartShape as TCustomRectShape) do
+  begin
+    FStartOrigin := Origin;
+    FStartXAxis := XAxis;
+    FStartYAxis := YAxis;
+    FStartFixedRatio := FixedRatio;
+  end;
+end;
+
+procedure TCustomRectShapeDiff.ComputeDiff(AEndShape: TVectorShape);
+begin
+  with (AEndShape as TCustomRectShape) do
+  begin
+    FEndOrigin := Origin;
+    FEndXAxis := XAxis;
+    FEndYAxis := YAxis;
+    FEndFixedRatio := FixedRatio;
+  end;
+end;
+
+procedure TCustomRectShapeDiff.Apply(AStartShape: TVectorShape);
+begin
+  with (AStartShape as TCustomRectShape) do
+  begin
+    BeginUpdate;
+    FOrigin := FEndOrigin;
+    FXAxis := FEndXAxis;
+    FYAxis := FEndYAxis;
+    FFixedRatio := FEndFixedRatio;
+    EndUpdate;
+  end;
+end;
+
+procedure TCustomRectShapeDiff.Unapply(AEndShape: TVectorShape);
+begin
+  with (AEndShape as TCustomRectShape) do
+  begin
+    BeginUpdate;
+    FOrigin := FStartOrigin;
+    FXAxis := FStartXAxis;
+    FYAxis := FStartYAxis;
+    FFixedRatio := FStartFixedRatio;
+    EndUpdate;
+  end;
+end;
+
+procedure TCustomRectShapeDiff.Append(ADiff: TVectorShapeDiff);
+var
+  next: TCustomRectShapeDiff;
+begin
+  next := ADiff as TCustomRectShapeDiff;
+  FEndOrigin := next.FEndOrigin;
+  FEndXAxis := next.FEndXAxis;
+  FEndYAxis := next.FEndYAxis;
+  FEndFixedRatio := next.FEndFixedRatio;
+end;
+
 { TCustomRectShape }
 
 procedure TCustomRectShape.SetOrigin(AValue: TPointF);
@@ -151,7 +309,7 @@ var
   t: TAffineMatrix;
 begin
   if FOrigin=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TCustomRectShapeDiff);
   delta := AValue - FOrigin;
   t := AffineMatrixTranslation(delta.x, delta.y);
   FOrigin := AValue;
@@ -229,7 +387,7 @@ begin
       if FFixedRatio <> curRatio then
       begin
         ratioFactor := FFixedRatio/curRatio;
-        BeginUpdate;
+        BeginUpdate(TCustomRectShapeDiff);
         refPoint := Origin + (XAxis-Origin)*ACenterX + (YAxis-Origin)*ACenterY;
         if (ACenterX=0) and (ACenterY=0) then fracPower := 1/2
         else fracPower := abs(ACenterY)/(abs(ACenterX)+abs(ACenterY));
@@ -257,7 +415,7 @@ var
   newSize: Single;
   u: TPointF;
 begin
-  BeginUpdate;
+  BeginUpdate(TCustomRectShapeDiff);
   if AllowShearTransform and ((ssAlt in AShift) or (FXUnitBackup = PointF(0,0))) then
   begin
     FXAxis := FOriginBackup + AFactor*(ANewCoord - FOriginBackup);
@@ -295,7 +453,7 @@ var
   newSizeY: Single;
   u: TPointF;
 begin
-  BeginUpdate;
+  BeginUpdate(TCustomRectShapeDiff);
   if AllowShearTransform and ((ssAlt in AShift) or (FYUnitBackup = PointF(0,0))) then
   begin
     FYAxis := FOriginBackup + AFactor*(ANewCoord - FOriginBackup);
@@ -335,7 +493,7 @@ var
   newSize, prevCornerVect, newCornerVect: TPointF;
   angle,deltaAngle, zoom: single;
 begin
-  BeginUpdate;
+  BeginUpdate(TCustomRectShapeDiff);
   if (ssAlt in AShift) and (VectDet(FXUnitBackup,FYUnitBackup)<>0) and (FXSizeBackup<>0) and (FYSizeBackup<>0) then
   begin
     prevCornerVect := AFactorX*(FXAxisBackup - FOriginBackup) + AFactorY*(FYAxisBackup - FOriginBackup);
@@ -468,7 +626,7 @@ end;
 
 procedure TCustomRectShape.Transform(AMatrix: TAffineMatrix);
 begin
-  BeginUpdate;
+  BeginUpdate(TCustomRectShapeDiff);
   FOrigin := AMatrix*FOrigin;
   FXAxis := AMatrix*FXAxis;
   FYAxis := AMatrix*FYAxis;
@@ -511,7 +669,7 @@ end;
 
 procedure TCustomRectShape.QuickDefine(const APoint1, APoint2: TPointF);
 begin
-  BeginUpdate;
+  BeginUpdate(TCustomRectShapeDiff);
   FOrigin := (APoint1+APoint2)*0.5;
   FXAxis := PointF(APoint2.X,FOrigin.Y);
   FYAxis := PointF(FOrigin.X,APoint2.Y);
@@ -1026,7 +1184,7 @@ end;
 procedure TPhongShape.SetShapeKind(AValue: TPhongShapeKind);
 begin
   if FShapeKind=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TPhongShapeDiff);
   FShapeKind:=AValue;
   EndUpdate;
 end;
@@ -1040,7 +1198,7 @@ end;
 procedure TPhongShape.SetBorderSizePercent(AValue: single);
 begin
   if FBorderSizePercent=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TPhongShapeDiff);
   FBorderSizePercent:=AValue;
   EndUpdate;
 end;
@@ -1048,7 +1206,7 @@ end;
 procedure TPhongShape.SetLightPosition(AValue: TPointF);
 begin
   if FLightPosition=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TPhongShapeDiff);
   FLightPosition:=AValue;
   EndUpdate;
 end;
@@ -1056,7 +1214,7 @@ end;
 procedure TPhongShape.SetShapeAltitudePercent(AValue: single);
 begin
   if FShapeAltitudePercent=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TPhongShapeDiff);
   FShapeAltitudePercent:=AValue;
   EndUpdate;
 end;
@@ -1351,9 +1509,9 @@ end;
 
 procedure TPhongShape.Transform(AMatrix: TAffineMatrix);
 begin
-  BeginUpdate;
-  inherited Transform(AMatrix);
+  BeginUpdate(TPhongShapeDiff);
   LightPosition := AMatrix*LightPosition;
+  inherited Transform(AMatrix);
   EndUpdate;
 end;
 

+ 295 - 22
lazpaintcontrols/lcvectortextshapes.pas

@@ -12,6 +12,65 @@ const
   AlwaysVectorialText = true;
 
 type
+  TTextShape = class;
+
+  { TTextShapeFontDiff }
+
+  TTextShapeFontDiff = class(TVectorShapeDiff)
+  protected
+    FFontBidiModeBefore: TFontBidiMode;
+    FFontEmHeightBefore: single;
+    FFontNameBefore: string;
+    FFontStyleBefore: TFontStyles;
+    FFontBidiModeAfter: TFontBidiMode;
+    FFontEmHeightAfter: single;
+    FFontNameAfter: string;
+    FFontStyleAfter: TFontStyles;
+  public
+    constructor Create(AStartShape: TVectorShape); override;
+    procedure ComputeDiff(AEndShape: TVectorShape); override;
+    procedure Apply(AStartShape: TVectorShape); override;
+    procedure Unapply(AEndShape: TVectorShape); override;
+    procedure Append(ADiff: TVectorShapeDiff); override;
+  end;
+
+  { TTextShapePhongDiff }
+
+  TTextShapePhongDiff = class(TVectorShapeDiff)
+  protected
+    FAltitudePercentBefore: single;
+    FPenPhongBefore: boolean;
+    FLightPositionBefore: TPointF;
+    FAltitudePercentAfter: single;
+    FPenPhongAfter: boolean;
+    FLightPositionAfter: TPointF;
+  public
+    constructor Create(AStartShape: TVectorShape); override;
+    procedure ComputeDiff(AEndShape: TVectorShape); override;
+    procedure Apply(AStartShape: TVectorShape); override;
+    procedure Unapply(AEndShape: TVectorShape); override;
+    procedure Append(ADiff: TVectorShapeDiff); override;
+  end;
+
+  { TTextShapeTextDiff }
+
+  TTextShapeTextDiff = class(TVectorShapeDiff)
+  protected
+    FTextBefore: string;
+    FSelStartBefore,FSelEndBefore: integer;
+    FVertAlignBefore: TTextLayout;
+    FParaAlignBefore: array of TBidiTextAlignment;
+    FTextAfter: string;
+    FSelStartAfter,FSelEndAfter: integer;
+    FVertAlignAfter: TTextLayout;
+    FParaAlignAfter: array of TBidiTextAlignment;
+  public
+    constructor Create(AStartShape: TVectorShape); override;
+    procedure ComputeDiff(AEndShape: TVectorShape); override;
+    procedure Apply(AStartShape: TVectorShape); override;
+    procedure Unapply(AEndShape: TVectorShape); override;
+    procedure Append(ADiff: TVectorShapeDiff); override;
+  end;
 
   { TTextShape }
 
@@ -19,18 +78,18 @@ type
   private
     FAltitudePercent: single;
     FPenPhong: boolean;
+    FLightPosition: TPointF;
     FFontBidiMode: TFontBidiMode;
     FFontEmHeight: single;
     FFontName: string;
     FFontStyle: TFontStyles;
-    FLightPosition: TPointF;
     FText: string;
     FSelStart,FSelEnd: integer;
+    FVertAlign: TTextLayout;
     FEnteringUnicode: boolean;
     FUnicodeValue: cardinal;
     FUnicodeDigitCount: integer;
     FMouseSelecting: boolean;
-    FVertAlign: TTextLayout;
     function GetBidiParagraphAlignment: TBidiTextAlignment;
     function GetCanPasteSelection: boolean;
     function GetHasSelection: boolean;
@@ -52,7 +111,7 @@ type
     FTextLayout: TBidiTextLayout;
     FFontRenderer: TBGRACustomFontRenderer;
     FGlobalMatrix: TAffineMatrix;
-    procedure DoOnChange(ABoundsBefore: TRectF); override;
+    procedure DoOnChange(ABoundsBefore: TRectF; ADiff: TVectorShapeDiff); override;
     procedure SetGlobalMatrix(AMatrix: TAffineMatrix);
     function PenVisible(AAssumePenFill: boolean = false): boolean;
     function AllowShearTransform: boolean; override;
@@ -169,12 +228,224 @@ begin
   else result := fbmAuto;
 end;
 
+{ TTextShapeTextDiff }
+
+constructor TTextShapeTextDiff.Create(AStartShape: TVectorShape);
+var
+  tl: TBidiTextLayout;
+  i: Integer;
+begin
+  with (AStartShape as TTextShape) do
+  begin
+    FTextBefore:= FText;
+    FVertAlignBefore:= FVertAlign;
+    tl := GetTextLayout;
+    FSelStartBefore := FSelStart;
+    FSelEndBefore:= FSelEnd;
+    setlength(FParaAlignBefore, tl.ParagraphCount);
+    for i := 0 to high(FParaAlignBefore) do
+      FParaAlignBefore[i] := tl.ParagraphAlignment[i];
+  end;
+end;
+
+procedure TTextShapeTextDiff.ComputeDiff(AEndShape: TVectorShape);
+var
+  tl: TBidiTextLayout;
+  i: Integer;
+begin
+  with (AEndShape as TTextShape) do
+  begin
+    FTextAfter:= FText;
+    FVertAlignAfter:= FVertAlign;
+    FSelStartAfter := FSelStart;
+    FSelEndAfter:= FSelEnd;
+    tl := GetTextLayout;
+    setlength(FParaAlignAfter, tl.ParagraphCount);
+    for i := 0 to high(FParaAlignAfter) do
+      FParaAlignAfter[i] := tl.ParagraphAlignment[i];
+  end;
+end;
+
+procedure TTextShapeTextDiff.Apply(AStartShape: TVectorShape);
+var
+  tl: TBidiTextLayout;
+  i: Integer;
+begin
+  with (AStartShape as TTextShape) do
+  begin
+    BeginUpdate;
+    FreeAndNil(FTextLayout);
+    FText := FTextAfter;
+    FVertAlign := FVertAlignAfter;
+    FSelStart := FSelStartAfter;
+    FSelEnd := FSelEndAfter;
+    tl := GetTextLayout;
+    for i := 0 to min(length(FParaAlignAfter),tl.ParagraphCount)-1 do
+      tl.ParagraphAlignment[i] := FParaAlignAfter[i];
+    EndUpdate;
+  end;
+end;
+
+procedure TTextShapeTextDiff.Unapply(AEndShape: TVectorShape);
+var
+  tl: TBidiTextLayout;
+  i: Integer;
+begin
+  with (AEndShape as TTextShape) do
+  begin
+    BeginUpdate;
+    FreeAndNil(FTextLayout);
+    FText := FTextBefore;
+    FVertAlign := FVertAlignBefore;
+    FSelStart := FSelStartBefore;
+    FSelEnd := FSelEndBefore;
+    tl := GetTextLayout;
+    for i := 0 to min(length(FParaAlignBefore),tl.ParagraphCount)-1 do
+      tl.ParagraphAlignment[i] := FParaAlignBefore[i];
+    EndUpdate;
+  end;
+end;
+
+procedure TTextShapeTextDiff.Append(ADiff: TVectorShapeDiff);
+var
+  next: TTextShapeTextDiff;
+  i: Integer;
+begin
+  next := ADiff as TTextShapeTextDiff;
+  FTextAfter := next.FTextAfter;
+  FVertAlignAfter := next.FVertAlignAfter;
+  FSelStartAfter := next.FSelStartAfter;
+  FSelEndAfter := next.FSelEndAfter;
+  setlength(FParaAlignAfter, length(next.FParaAlignAfter));
+  for i := 0 to high(FParaAlignAfter) do
+    FParaAlignAfter[i] := next.FParaAlignAfter[i];
+end;
+
+{ TTextShapePhongDiff }
+
+constructor TTextShapePhongDiff.Create(AStartShape: TVectorShape);
+begin
+  with (AStartShape as TTextShape) do
+  begin
+    FAltitudePercentBefore := FAltitudePercent;
+    FPenPhongBefore := FPenPhong;
+    FLightPositionBefore := FLightPosition;
+  end;
+end;
+
+procedure TTextShapePhongDiff.ComputeDiff(AEndShape: TVectorShape);
+begin
+  with (AEndShape as TTextShape) do
+  begin
+    FAltitudePercentAfter := FAltitudePercent;
+    FPenPhongAfter := FPenPhong;
+    FLightPositionAfter := FLightPosition;
+  end;
+end;
+
+procedure TTextShapePhongDiff.Apply(AStartShape: TVectorShape);
+begin
+  with (AStartShape as TTextShape) do
+  begin
+    BeginUpdate;
+    FAltitudePercent := FAltitudePercentAfter;
+    FPenPhong := FPenPhongAfter;
+    FLightPosition := FLightPositionAfter;
+    EndUpdate;
+  end;
+end;
+
+procedure TTextShapePhongDiff.Unapply(AEndShape: TVectorShape);
+begin
+  with (AEndShape as TTextShape) do
+  begin
+    BeginUpdate;
+    FAltitudePercent := FAltitudePercentBefore;
+    FPenPhong := FPenPhongBefore;
+    FLightPosition := FLightPositionBefore;
+    EndUpdate;
+  end;
+end;
+
+procedure TTextShapePhongDiff.Append(ADiff: TVectorShapeDiff);
+var
+  next: TTextShapePhongDiff;
+begin
+  next := ADiff as TTextShapePhongDiff;
+  FAltitudePercentAfter:= next.FAltitudePercentAfter;
+  FPenPhongAfter:= next.FPenPhongAfter;
+  FLightPositionAfter:= next.FLightPositionAfter;
+end;
+
+{ TTextShapeFontDiff }
+
+constructor TTextShapeFontDiff.Create(AStartShape: TVectorShape);
+begin
+  with (AStartShape as TTextShape) do
+  begin
+    FFontBidiModeBefore:= FFontBidiMode;
+    FFontEmHeightBefore:= FFontEmHeight;
+    FFontNameBefore:= FFontName;
+    FFontStyleBefore:= FFontStyle;
+  end;
+end;
+
+procedure TTextShapeFontDiff.ComputeDiff(AEndShape: TVectorShape);
+begin
+  with (AEndShape as TTextShape) do
+  begin
+    FFontBidiModeAfter:= FFontBidiMode;
+    FFontEmHeightAfter:= FFontEmHeight;
+    FFontNameAfter:= FFontName;
+    FFontStyleAfter:= FFontStyle;
+  end;
+end;
+
+procedure TTextShapeFontDiff.Apply(AStartShape: TVectorShape);
+begin
+  with (AStartShape as TTextShape) do
+  begin
+    BeginUpdate;
+    FFontBidiMode := FFontBidiModeAfter;
+    FFontEmHeight := FFontEmHeightAfter;
+    FFontName := FFontNameAfter;
+    FFontStyle := FFontStyleAfter;
+    if Assigned(FTextLayout) then FTextLayout.InvalidateLayout;
+    EndUpdate;
+  end;
+end;
+
+procedure TTextShapeFontDiff.Unapply(AEndShape: TVectorShape);
+begin
+  with (AEndShape as TTextShape) do
+  begin
+    BeginUpdate;
+    FFontBidiMode := FFontBidiModeBefore;
+    FFontEmHeight := FFontEmHeightBefore;
+    FFontName := FFontNameBefore;
+    FFontStyle := FFontStyleBefore;
+    if Assigned(FTextLayout) then FTextLayout.InvalidateLayout;
+    EndUpdate;
+  end;
+end;
+
+procedure TTextShapeFontDiff.Append(ADiff: TVectorShapeDiff);
+var
+  next: TTextShapeFontDiff;
+begin
+  next := ADiff as TTextShapeFontDiff;
+  FFontBidiModeAfter := next.FFontBidiModeAfter;
+  FFontEmHeightAfter := next.FFontEmHeightAfter;
+  FFontNameAfter := next.FFontNameAfter;
+  FFontStyleAfter := next.FFontStyleAfter;
+end;
+
 { TTextShape }
 
 procedure TTextShape.SetText(AValue: string);
 begin
   if FText=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapeTextDiff);
   FText:=AValue;
   FSelStart:=0;
   FSelEnd :=0;
@@ -185,7 +456,7 @@ end;
 procedure TTextShape.SetFontBidiMode(AValue: TFontBidiMode);
 begin
   if FFontBidiMode=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapeFontDiff);
   FFontBidiMode:=AValue;
   EndUpdate;
 end;
@@ -240,7 +511,7 @@ begin
   if AValue < 0 then AValue := 0;
   if AValue > 100 then AValue := 100;
   if FAltitudePercent=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapePhongDiff);
   FAltitudePercent:=AValue;
   EndUpdate;
 end;
@@ -248,7 +519,7 @@ end;
 procedure TTextShape.SetPenPhong(AValue: boolean);
 begin
   if FPenPhong=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapePhongDiff);
   FPenPhong:=AValue;
   EndUpdate;
 end;
@@ -256,7 +527,7 @@ end;
 procedure TTextShape.SetFontEmHeight(AValue: single);
 begin
   if FFontEmHeight=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapeFontDiff);
   FFontEmHeight:=AValue;
   if Assigned(FTextLayout) then FTextLayout.InvalidateLayout;
   EndUpdate;
@@ -265,7 +536,7 @@ end;
 procedure TTextShape.SetFontName(AValue: string);
 begin
   if FFontName=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapeFontDiff);
   FFontName:=AValue;
   if Assigned(FTextLayout) then FTextLayout.InvalidateLayout;
   EndUpdate;
@@ -274,7 +545,7 @@ end;
 procedure TTextShape.SetFontStyle(AValue: TFontStyles);
 begin
   if FFontStyle=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapeFontDiff);
   FFontStyle:=AValue;
   if Assigned(FTextLayout) then FTextLayout.InvalidateLayout;
   EndUpdate;
@@ -303,7 +574,7 @@ begin
   begin
     if not needUpdate then
     begin
-      BeginUpdate;
+      BeginUpdate(TTextShapeTextDiff);
       needUpdate := true;
     end;
     tl.ParagraphAlignment[i] := AValue;
@@ -314,7 +585,7 @@ end;
 procedure TTextShape.SetLightPosition(AValue: TPointF);
 begin
   if FLightPosition=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapePhongDiff);
   FLightPosition:=AValue;
   EndUpdate;
 end;
@@ -351,7 +622,7 @@ begin
     begin
       if not needUpdate then
       begin
-        BeginUpdate;
+        BeginUpdate(TTextShapeTextDiff);
         needUpdate := true;
       end;
       tl.ParagraphAlignment[i] := bidiAlign;
@@ -363,12 +634,12 @@ end;
 procedure TTextShape.SetVertAlign(AValue: TTextLayout);
 begin
   if FVertAlign=AValue then Exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapeTextDiff);
   FVertAlign:=AValue;
   EndUpdate;
 end;
 
-procedure TTextShape.DoOnChange(ABoundsBefore: TRectF);
+procedure TTextShape.DoOnChange(ABoundsBefore: TRectF; ADiff: TVectorShapeDiff);
 var freeRenderer: boolean;
 begin
   if Assigned(FFontRenderer) then
@@ -390,7 +661,7 @@ begin
         FTextLayout.FontRenderer := GetFontRenderer;
     end;
   end;
-  inherited DoOnChange(ABoundsBefore);
+  inherited DoOnChange(ABoundsBefore, ADiff);
 end;
 
 procedure TTextShape.SetGlobalMatrix(AMatrix: TAffineMatrix);
@@ -527,7 +798,7 @@ var
   delCount, selLeft: Integer;
 begin
   if UserMode <> vsuEditText then exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapeTextDiff);
   selLeft := Min(FSelStart,FSelEnd);
   if selLeft > 0 then
   begin
@@ -546,7 +817,7 @@ var
   tl: TBidiTextLayout;
 begin
   if UserMode <> vsuEditText then exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapeTextDiff);
   selRight := Max(FSelStart,FSelEnd);
   tl := GetTextLayout;
   if selRight+ACount <= tl.CharCount then
@@ -566,7 +837,7 @@ begin
   if UserMode <> vsuEditText then exit;
   if FSelStart <> FSelEnd then
   begin
-    BeginUpdate;
+    BeginUpdate(TTextShapeTextDiff);
     selLeft := Min(FSelStart,FSelEnd);
     GetTextLayout.DeleteText(selLeft, Abs(FSelEnd-FSelStart));
     FText := GetTextLayout.TextUTF8;
@@ -581,7 +852,7 @@ var
   insertCount: Integer;
 begin
   if UserMode <> vsuEditText then exit;
-  BeginUpdate;
+  BeginUpdate(TTextShapeTextDiff);
   DeleteSelectedText;
   insertCount := GetTextLayout.InsertText(ATextUTF8, FSelStart);
   FText := GetTextLayout.TextUTF8;
@@ -1369,7 +1640,7 @@ procedure TTextShape.SetFontNameAndStyle(AFontName: string;
 begin
   if (AFontName <> FFontName) or (AFontStyle <> FFontStyle) then
   begin
-    BeginUpdate;
+    BeginUpdate(TTextShapeFontDiff);
     FFontName := AFontName;
     FFontStyle:= AFontStyle;
     EndUpdate;
@@ -1424,10 +1695,12 @@ var
   zoom: Single;
 begin
   BeginUpdate;
-  inherited Transform(AMatrix);
+  AddDiffHandler(TTextShapeFontDiff);
+  AddDiffHandler(TTextShapePhongDiff);
   zoom := (VectLen(AMatrix[1,1],AMatrix[2,1])+VectLen(AMatrix[1,2],AMatrix[2,2]))/2;
   FontEmHeight:= zoom*FontEmHeight;
   LightPosition := AMatrix*LightPosition;
+  inherited Transform(AMatrix);
   EndUpdate;
 end;
 

+ 90 - 6
vectoredit/umain.pas

@@ -11,7 +11,7 @@ uses
   BGRABitmap, BGRABitmapTypes, BGRAGraphics, BGRALazPaint, BGRALayerOriginal,
   BGRATransform, BGRAGradientScanner, LCVectorOriginal, LCVectorShapes,
   LCVectorRectShapes, LCVectorPolyShapes, LCVectorTextShapes,
-  LCVectorialFillControl, LCVectorialFill;
+  LCVectorialFillControl, LCVectorialFill, fgl;
 
 const
   RenderDelayMs = 100; //minimum delay between the end of the last rendering and the beginning of the next rendering
@@ -43,6 +43,8 @@ const
     ('Auto', 'Left to right', 'Right to left');
 
 type
+  TOriginalDiffList = specialize TFPGObjectList<TBGRAOriginalDiff>;
+
   { TForm1 }
 
   TForm1 = class(TForm)
@@ -145,7 +147,7 @@ type
     procedure OutlineFillControlResize(Sender: TObject);
     procedure PanelFileResize(Sender: TObject);
     procedure PanelShapeResize(Sender: TObject);
-   procedure ShapeBringToFrontExecute(Sender: TObject);
+    procedure ShapeBringToFrontExecute(Sender: TObject);
     procedure ShapeMoveDownExecute(Sender: TObject);
     procedure ShapeMoveUpExecute(Sender: TObject);
     procedure ShapeSendToBackExecute(Sender: TObject);
@@ -222,7 +224,8 @@ type
     procedure OnClickSplineStyleItem(ASender: TObject);
     procedure OnEditingChange({%H-}ASender: TObject; AOriginal: TBGRALayerCustomOriginal);
     procedure OnEditorFocusChange(Sender: TObject);
-    procedure OnOriginalChange({%H-}ASender: TObject; AOriginal: TBGRALayerCustomOriginal);
+    procedure OnOriginalChange({%H-}ASender: TObject; AOriginal: TBGRALayerCustomOriginal;
+                               var ADiff: TBGRAOriginalDiff);
     procedure OnPhongBorderSizeChange(Sender: TObject; AByUser: boolean);
     procedure OnPhongShapeAltitudeChange(Sender: TObject; AByUser: boolean);
     procedure OnSelectShape(ASender: TObject; AShape: TVectorShape; APreviousShape: TVectorShape);
@@ -285,10 +288,16 @@ type
     procedure TextFontTextBoxExit(Sender: TObject);
     procedure NewImage(AWidth,AHeight: integer);
     procedure SetImage(AImage: TBGRALazPaintImage);
+    procedure AddDiff({%H-}AOriginal: TBGRALayerCustomOriginal; ADiff: TBGRAOriginalDiff);
   public
     { public declarations }
     img: TBGRALazPaintImage;
     FVectorLayerIndex: Integer;
+    FDiffList: TOriginalDiffList;
+    FDiffListPos,
+    FDiffListSavePos: integer;
+    FDiffAppend: boolean;
+    FDiffLastDate: TDateTime;
     filename: string;
     zoom: TAffineMatrix;
     newShape: TVectorShape;
@@ -301,6 +310,8 @@ type
     procedure DoCut;
     procedure DoPaste;
     procedure DoDelete;
+    procedure DoUndo;
+    procedure DoRedo;
     property vectorTransform: TAffineMatrix read GetVectorTransform;
     property penWidth: single read GetPenWidth write SetPenWidth;
     property penStyle: TBGRAPenStyle read GetPenStyle write SetPenStyle;
@@ -493,6 +504,7 @@ var
 begin
   FocusView;
   mouseState:= Shift;
+  if [ssLeft,ssRight]*mouseState = [] then FDiffAppend := false;
   imgPtF := VirtualScreenToImgCoord(X,Y);
   SetEditorGrid(ssCtrl in Shift);
   img.MouseDown(Button=mbRight, Shift, imgPtF.x, imgPtF.y, cur, handled);
@@ -757,6 +769,7 @@ var
   vectorFill: TVectorialFill;
 begin
   mouseState:= Shift;
+  if [ssLeft,ssRight]*mouseState = [] then FDiffAppend := false;
   imgPtF := VirtualScreenToImgCoord(X,Y);
   SetEditorGrid(ssCtrl in Shift);
   img.MouseMove(Shift, imgPtF.X, imgPtF.Y, cur, handled);
@@ -822,6 +835,7 @@ var
   addedShape, curShape: TVectorShape;
 begin
   mouseState:= Shift;
+  if [ssLeft,ssRight]*mouseState = [] then FDiffAppend := false;
   imgPtF := VirtualScreenToImgCoord(X,Y);
   SetEditorGrid(ssCtrl in Shift);
   img.MouseUp(Button = mbRight, Shift, imgPtF.X, imgPtF.Y, cur, handled);
@@ -891,6 +905,7 @@ begin
   RemoveExtendedStyleControls;
   if (newShape <> nil) and not shapeAdded then FreeAndNil(newShape);
   img.Free;
+  FDiffList.Free;
   FFlattened.Free;
   ButtonPenStyle.DropDownMenu := nil;
   FPenStyleMenu.Free;
@@ -948,6 +963,20 @@ begin
   begin
     Key := 0;
     DoDelete;
+  end else
+  if (Key = VK_Z) and ([ssCtrl,ssShift]*Shift=[ssCtrl]) and (FDiffListPos > 0) and
+    not FDiffAppend then
+  begin
+    Key := 0;
+    DoUndo;
+  end else
+  if ( ((Key = VK_Y) and ([ssCtrl,ssShift]*Shift=[ssCtrl])) or
+       ((Key = VK_Z) and ([ssCtrl,ssShift]*Shift=[ssCtrl,ssShift])) )
+     and Assigned(FDiffList) and (FDiffListPos < FDiffList.Count) and
+     not FDiffAppend then
+  begin
+    Key := 0;
+    DoRedo;
   end;
 end;
 
@@ -1158,7 +1187,8 @@ begin
   UpdateView(EmptyRect);
 end;
 
-procedure TForm1.OnOriginalChange(ASender: TObject; AOriginal: TBGRALayerCustomOriginal);
+procedure TForm1.OnOriginalChange(ASender: TObject; AOriginal: TBGRALayerCustomOriginal;
+  var ADiff: TBGRAOriginalDiff);
 var
   slowShape: boolean;
 begin
@@ -1170,6 +1200,8 @@ begin
       slowShape := vectorOriginal.SelectedShape.GetIsSlow(vectorTransform);
   end;
   RenderAndUpdate(slowShape);
+  AddDiff(AOriginal, ADiff);
+  ADiff := nil;
 end;
 
 procedure TForm1.OnPhongBorderSizeChange(Sender: TObject; AByUser: boolean);
@@ -1486,6 +1518,10 @@ end;
 procedure TForm1.SetImage(AImage: TBGRALazPaintImage);
 begin
   FreeAndNil(img);
+  FreeAndNil(FDiffList);
+  FDiffListPos := 0;
+  FDiffListSavePos := 0;
+  FDiffAppend := false;
   img := AImage;
   img.OnOriginalEditingChange:= @OnEditingChange;
   img.EditorFocused:= BGRAVirtualScreen1.Focused;
@@ -1495,6 +1531,29 @@ begin
   ImageChangesCompletely;
 end;
 
+procedure TForm1.AddDiff(AOriginal: TBGRALayerCustomOriginal;
+  ADiff: TBGRAOriginalDiff);
+const DiffMinDelay = 1000 / (1000*60*60*24);
+begin
+  if ADiff = nil then exit;
+  if FDiffList=nil then FDiffList := TOriginalDiffList.Create;
+  while FDiffList.Count > FDiffListPos do FDiffList.Delete(FDiffList.Count-1);
+  if FDiffListSavePos > FDiffList.Count then FDiffListSavePos:= maxLongint;
+  if (FDiffList.Count>0) and FDiffList[FDiffList.Count-1].CanAppend(ADiff) and
+    (FDiffAppend or (Now < FDiffLastDate+DiffMinDelay)) then
+  begin
+    FDiffList[FDiffList.Count-1].Append(ADiff);
+    ADiff.Free;
+  end
+  else
+  begin
+    FDiffListPos := FDiffList.Add(ADiff)+1;
+    FDiffAppend:= [ssLeft,ssRight]*mouseState <> [];
+  end;
+  FDiffLastDate := Now;
+  UpdateTitleBar;
+end;
+
 procedure TForm1.TextStyleClick(Sender: TObject);
 var
   btn: TToolButton;
@@ -1876,11 +1935,15 @@ begin
 end;
 
 procedure TForm1.UpdateTitleBar;
+var
+  modifStr: string;
 begin
+  if Assigned(FDiffList) and (FDiffListPos <> FDiffListSavePos) then
+    modifStr := '*' else modifStr := '';
   if filename = '' then
-    Caption := baseCaption + ' - New image - ' + inttostr(img.Width)+'x'+inttostr(img.Height)
+    Caption := baseCaption + ' - New image'+modifStr+' - ' + inttostr(img.Width)+'x'+inttostr(img.Height)
   else
-    Caption := baseCaption + ' - ' + filename + ' - ' + inttostr(img.Width)+'x'+inttostr(img.Height);
+    Caption := baseCaption + ' - ' + filename + modifStr+' - ' + inttostr(img.Width)+'x'+inttostr(img.Height);
 end;
 
 procedure TForm1.ImageChangesCompletely;
@@ -2249,5 +2312,26 @@ begin
     vectorOriginal.SelectedShape.Remove;
 end;
 
+procedure TForm1.DoUndo;
+var
+  diff: TBGRAOriginalDiff;
+begin
+  if FDiffListPos <= 0 then exit;
+  dec(FDiffListPos);
+  diff := FDiffList[FDiffListPos];
+  diff.Unapply(vectorOriginal);
+  UpdateTitleBar;
+end;
+
+procedure TForm1.DoRedo;
+var
+  diff: TBGRAOriginalDiff;
+begin
+  diff := FDiffList[FDiffListPos];
+  diff.Apply(vectorOriginal);
+  inc(FDiffListPos);
+  UpdateTitleBar;
+end;
+
 end.
 

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff