Bladeren bron

undo group

johann 5 jaren geleden
bovenliggende
commit
f689e113fc

+ 119 - 46
lazpaint/image/uimage.pas

@@ -47,8 +47,9 @@ type
     FRenderedImage: TBGRABitmap;
     FRenderedImage: TBGRABitmap;
     FRenderedImageInvalidated: TRect;
     FRenderedImageInvalidated: TRect;
     FOnImageChanged: TLazPaintImageObservable;
     FOnImageChanged: TLazPaintImageObservable;
-    FUndoList: TList;
+    FUndoList: TComposedImageDifference;
     FUndoPos: integer;
     FUndoPos: integer;
+    FNextUndoBegin: integer;
     FRenderUpdateRectInPicCoord, FRenderUpdateRectInVSCoord: TRect;
     FRenderUpdateRectInPicCoord, FRenderUpdateRectInVSCoord: TRect;
     FOnCurrentFilenameChanged: TOnCurrentFilenameChanged;
     FOnCurrentFilenameChanged: TOnCurrentFilenameChanged;
 
 
@@ -113,7 +114,6 @@ type
     procedure CompressUndoIfNecessary;
     procedure CompressUndoIfNecessary;
     procedure NotifyException(AFunctionName: string; AException: Exception);
     procedure NotifyException(AFunctionName: string; AException: Exception);
     procedure SetSelectionTransform(ATransform: TAffineMatrix);
     procedure SetSelectionTransform(ATransform: TAffineMatrix);
-    procedure ClearUndoAfter;
     procedure UpdateIconFileUTF8(AFilename: string; AOutputFilename: string = '');
     procedure UpdateIconFileUTF8(AFilename: string; AOutputFilename: string = '');
     procedure UpdateTiffFileUTF8(AFilename: string; AOutputFilename: string = '');
     procedure UpdateTiffFileUTF8(AFilename: string; AOutputFilename: string = '');
     procedure UpdateGifFileUTF8(AFilename: string; AOutputFilename: string = '');
     procedure UpdateGifFileUTF8(AFilename: string; AOutputFilename: string = '');
@@ -140,6 +140,8 @@ type
     function CanRedo: boolean;
     function CanRedo: boolean;
     procedure Undo;
     procedure Undo;
     procedure Redo;
     procedure Redo;
+    procedure DoBegin;
+    procedure DoEnd(out ADoFound: boolean; out ASomethingDone: boolean);
     procedure ClearUndo;
     procedure ClearUndo;
     procedure CompressUndo;
     procedure CompressUndo;
     function UsedMemory: int64;
     function UsedMemory: int64;
@@ -699,8 +701,8 @@ begin
   self.FrameCount := ASavedFrameCount;
   self.FrameCount := ASavedFrameCount;
   for i := 0 to FUndoList.Count-1 do
   for i := 0 to FUndoList.Count-1 do
   begin
   begin
-    TCustomImageDifference(FUndoList[i]).SavedBefore := (i = FUndoPos+1);
-    TCustomImageDifference(FUndoList[i]).SavedAfter := (i = FUndoPos);
+    FUndoList[i].SavedBefore := (i = FUndoPos+1);
+    FUndoList[i].SavedAfter := (i = FUndoPos);
   end;
   end;
   OnImageChanged.NotifyObservers;
   OnImageChanged.NotifyObservers;
 end;
 end;
@@ -768,6 +770,8 @@ end;
 procedure TLazPaintImage.AddUndo(AUndoAction: TCustomImageDifference);
 procedure TLazPaintImage.AddUndo(AUndoAction: TCustomImageDifference);
 var
 var
   prevAction: TCustomImageDifference;
   prevAction: TCustomImageDifference;
+  prevGroup: TComposedImageDifference;
+  prevActionIndex: Integer;
 begin
 begin
   if AUndoAction <> nil then
   if AUndoAction <> nil then
   begin
   begin
@@ -776,16 +780,32 @@ begin
       AUndoAction.Free;
       AUndoAction.Free;
       exit;
       exit;
     end;
     end;
-    if FUndoPos > -1 then
+    prevGroup := FUndoList;
+    prevActionIndex := FUndoPos;
+    if prevActionIndex > -1 then
+    begin
+      prevAction := prevGroup[prevActionIndex];
+      while (prevAction is TComposedImageDifference) and
+        TComposedImageDifference(prevAction).Agglutinate do
+      begin
+        prevGroup := TComposedImageDifference(prevAction);
+        prevActionIndex := prevGroup.Count-1;
+        if prevActionIndex>=0 then
+          prevAction := prevGroup[prevActionIndex]
+        else
+          prevAction := nil;
+      end;
+    end else
+      prevAction := nil;
+    if assigned(prevAction) then
     begin
     begin
-      prevAction := TObject(FUndoList[FUndoPos]) as TCustomImageDifference;
       if IsInverseImageDiff(AUndoAction,prevAction) then
       if IsInverseImageDiff(AUndoAction,prevAction) then
       begin
       begin
         //writeln('Inverse');
         //writeln('Inverse');
         AUndoAction.Free;
         AUndoAction.Free;
         FCurrentState.saved := prevAction.SavedBefore;
         FCurrentState.saved := prevAction.SavedBefore;
-        Dec(FUndoPos);
-        ClearUndoAfter;
+        prevGroup.DeleteFrom(prevActionIndex);
+        if prevGroup = FUndoList then FUndoPos := prevActionIndex-1;
         exit;
         exit;
       end else
       end else
       if not prevAction.savedAfter and TryCombineImageDiff(AUndoAction,prevAction) then
       if not prevAction.savedAfter and TryCombineImageDiff(AUndoAction,prevAction) then
@@ -795,21 +815,30 @@ begin
         begin
         begin
           //writeln('Inverse (combine)');
           //writeln('Inverse (combine)');
           FCurrentState.saved := prevAction.SavedBefore;
           FCurrentState.saved := prevAction.SavedBefore;
-          Dec(FUndoPos);
-          ClearUndoAfter;
+          prevGroup.DeleteFrom(prevActionIndex);
+          if prevGroup = FUndoList then FUndoPos := prevActionIndex-1;
         end;
         end;
         exit;
         exit;
       end;
       end;
     end;
     end;
-    ClearUndoAfter;
-    if FUndoList.Count >= MaxUndoCount then
+    prevGroup.DeleteFrom(prevActionIndex+1);
+    if prevGroup.TotalCount >= MaxUndoCount then
     begin
     begin
-      FUndoList.Delete(0);
-      FUndoList.Add(AUndoAction);
+      if prevGroup = FUndoList then
+      begin
+        FUndoList.Delete(0);
+        FUndoList.Add(AUndoAction);
+      end else
+      begin
+        MessagePopup(rsTooManyActions, 4000);
+        AUndoAction.UnapplyTo(FCurrentState);
+        InvalidateImageDifference(AUndoAction);
+        exit;
+      end;
     end else
     end else
     begin
     begin
-      FUndoList.Add(AUndoAction);
-      inc(FUndoPos);
+      prevGroup.Add(AUndoAction);
+      if prevGroup = FUndoList then inc(FUndoPos);
     end;
     end;
     //writeln(AUndoAction.ToString);
     //writeln(AUndoAction.ToString);
     FCurrentState.saved := AUndoAction.SavedAfter;
     FCurrentState.saved := AUndoAction.SavedAfter;
@@ -823,7 +852,7 @@ begin
   for i := 0 to FUndoList.Count-1 do
   for i := 0 to FUndoList.Count-1 do
     if UsedMemory <= MaxUsedMemoryWithoutCompression then break else
     if UsedMemory <= MaxUsedMemoryWithoutCompression then break else
     repeat
     repeat
-      if not (TObject(FUndoList[i]) as TStateDifference).TryCompress then break;
+      if not FUndoList[i].TryCompress then break;
     until UsedMemory <= MaxUsedMemoryWithoutCompression;
     until UsedMemory <= MaxUsedMemoryWithoutCompression;
 end;
 end;
 
 
@@ -864,16 +893,6 @@ begin
   end;
   end;
 end;
 end;
 
 
-procedure TLazPaintImage.ClearUndoAfter;
-var I: integer;
-begin
-  for I := FUndoList.Count-1 downto FUndoPos+1 do
-  begin
-    TObject(FUndoList[i]).Free;
-    FUndoList.Delete(i);
-  end;
-end;
-
 procedure TLazPaintImage.SetLayerName(AIndex: integer; AValue: string);
 procedure TLazPaintImage.SetLayerName(AIndex: integer; AValue: string);
 begin
 begin
   AddUndo(FCurrentState.SetLayerName(AIndex,Avalue));
   AddUndo(FCurrentState.SetLayerName(AIndex,Avalue));
@@ -942,16 +961,31 @@ begin
 end;
 end;
 
 
 procedure TLazPaintImage.Undo;
 procedure TLazPaintImage.Undo;
-var diff: TCustomImageDifference;
+var prevAction: TCustomImageDifference;
+  prevGroup: TComposedImageDifference;
+  prevActionIndex: Integer;
 begin
 begin
   if CanUndo then
   if CanUndo then
   begin
   begin
     if not CheckNoAction then exit;
     if not CheckNoAction then exit;
     try
     try
-      diff := TCustomImageDifference(FUndoList[FUndoPos]);
-      diff.UnapplyTo(FCurrentState);
-      Dec(FUndoPos);
-      InvalidateImageDifference(diff);
+      prevGroup := FUndoList;
+      prevActionIndex := FUndoPos;
+      prevAction := prevGroup[prevActionIndex];
+      while (prevAction is TComposedImageDifference) and
+        TComposedImageDifference(prevAction).Agglutinate and
+        (TComposedImageDifference(prevAction).Count > 0) do
+      begin
+        prevGroup := TComposedImageDifference(prevAction);
+        prevActionIndex := prevGroup.Count-1;
+        prevAction := prevGroup[prevActionIndex];
+      end;
+      prevAction.UnapplyTo(FCurrentState);
+      InvalidateImageDifference(prevAction);
+      if prevGroup = FUndoList then
+        Dec(FUndoPos)
+      else
+        prevGroup.Delete(prevActionIndex);
     except
     except
       on ex:Exception do
       on ex:Exception do
       begin
       begin
@@ -1023,7 +1057,7 @@ begin
     if not CheckNoAction then exit;
     if not CheckNoAction then exit;
     try
     try
       inc(FUndoPos);
       inc(FUndoPos);
-      diff := TCustomImageDifference(FUndoList[FUndoPos]);
+      diff := FUndoList[FUndoPos];
       diff.ApplyTo(FCurrentState);
       diff.ApplyTo(FCurrentState);
       InvalidateImageDifference(diff);
       InvalidateImageDifference(diff);
     except
     except
@@ -1039,27 +1073,66 @@ begin
   end;
   end;
 end;
 end;
 
 
-procedure TLazPaintImage.ClearUndo;
-var i: integer;
+procedure TLazPaintImage.DoBegin;
 begin
 begin
-  try
-    for i := 0 to FUndoList.Count-1 do
-      TObject(FUndoList[i]).Free;
-  except
-    on ex: exception do
+  AddUndo(TComposedImageDifference.Create(True));
+end;
+
+procedure TLazPaintImage.DoEnd(out ADoFound: boolean; out ASomethingDone: boolean);
+var
+  curDiff, insideDiff: TCustomImageDifference;
+  curGroup: TComposedImageDifference;
+  curIndex: Integer;
+begin
+  ADoFound := false;
+  ASomethingDone := false;
+  if FUndoPos >= 0 then
+  begin
+    curGroup := FUndoList;
+    curIndex := FUndoPos;
+    curDiff := curGroup[curIndex];
+    if not ((curDiff is TComposedImageDifference) and
+      TComposedImageDifference(curDiff).Agglutinate) then
+        exit;
+    ADoFound:= true;
+    ASomethingDone := true;
+    repeat
+      insideDiff := TComposedImageDifference(curDiff).GetLast;
+      if (insideDiff <> nil) and (insideDiff is TComposedImageDifference) and
+         TComposedImageDifference(insideDiff).Agglutinate then
+      begin
+        curGroup := TComposedImageDifference(curDiff);
+        curIndex := curGroup.Count-1;
+        curDiff := insideDiff;
+      end
+      else
+        break;
+    until false;
+    TComposedImageDifference(curDiff).StopAgglutinate;
+    if TComposedImageDifference(curDiff).Count = 0 then
     begin
     begin
-      //ignore
+      curGroup.Delete(curIndex);
+      if curGroup = FUndoList then dec(FUndoPos);
+      ASomethingDone := false;
     end;
     end;
   end;
   end;
-  FUndoList.Clear;
-  FUndoPos := -1;
+end;
+
+procedure TLazPaintImage.ClearUndo;
+begin
+  try
+    FUndoList.Clear;
+    FUndoPos := -1;
+  except on ex:exception do
+    MessagePopup(ex.Message, 4000);
+  end;
 end;
 end;
 
 
 procedure TLazPaintImage.CompressUndo;
 procedure TLazPaintImage.CompressUndo;
 var i: integer;
 var i: integer;
 begin
 begin
   for i := 0 to FUndoList.Count-1 do
   for i := 0 to FUndoList.Count-1 do
-    if (TObject(FUndoList[i]) as TStateDifference).TryCompress then exit;
+    if FUndoList[i].TryCompress then exit;
 end;
 end;
 
 
 function TLazPaintImage.UsedMemory: int64;
 function TLazPaintImage.UsedMemory: int64;
@@ -1068,7 +1141,7 @@ begin
   result := 0;
   result := 0;
   if Assigned(FUndoList) then
   if Assigned(FUndoList) then
     for i := 0 to FUndoList.Count-1 do
     for i := 0 to FUndoList.Count-1 do
-      result += (TObject(FUndoList[i]) as TStateDifference).UsedMemory;
+      result += FUndoList[i].UsedMemory;
 end;
 end;
 
 
 function TLazPaintImage.CreateAction(AApplyOfsBefore: boolean;
 function TLazPaintImage.CreateAction(AApplyOfsBefore: boolean;
@@ -2146,7 +2219,7 @@ begin
   FOnSelectedLayerIndexChanged := nil;
   FOnSelectedLayerIndexChanged := nil;
   FOnStackChanged := nil;
   FOnStackChanged := nil;
   FOnImageChanged := TLazPaintImageObservable.Create(self);
   FOnImageChanged := TLazPaintImageObservable.Create(self);
-  FUndoList := TList.Create;
+  FUndoList := TComposedImageDifference.Create;
   FUndoPos := -1;
   FUndoPos := -1;
   ImageOffset := Point(0,0);
   ImageOffset := Point(0,0);
   FrameIndex := -1;
   FrameIndex := -1;

+ 23 - 0
lazpaint/image/uimageaction.pas

@@ -44,6 +44,8 @@ type
     function SmartZoom3: boolean;
     function SmartZoom3: boolean;
     procedure Undo;
     procedure Undo;
     procedure Redo;
     procedure Redo;
+    procedure DoBegin;
+    function DoEnd: boolean;
     procedure SetCurrentBitmap(bmp: TBGRABitmap; AUndoable: boolean;
     procedure SetCurrentBitmap(bmp: TBGRABitmap; AUndoable: boolean;
       ACaption: string = ''; AOpacity: byte = 255);
       ACaption: string = ''; AOpacity: byte = 255);
     procedure CropToSelectionAndLayer;
     procedure CropToSelectionAndLayer;
@@ -142,6 +144,8 @@ begin
 
 
   Scripting.RegisterScriptFunction('EditUndo',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditUndo',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditRedo',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditRedo',@GenericScriptFunction,ARegister);
+  Scripting.RegisterScriptFunction('EditDoBegin',@GenericScriptFunction,ARegister);
+  Scripting.RegisterScriptFunction('EditDoEnd',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditInvertSelection',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditInvertSelection',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditDeselect',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditDeselect',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditCopy',@GenericScriptFunction,ARegister);
   Scripting.RegisterScriptFunction('EditCopy',@GenericScriptFunction,ARegister);
@@ -221,6 +225,8 @@ begin
   if f = 'ImageSwapRedBlue' then SwapRedBlueAll else
   if f = 'ImageSwapRedBlue' then SwapRedBlueAll else
   if f = 'EditUndo' then Undo else
   if f = 'EditUndo' then Undo else
   if f = 'EditRedo' then Redo else
   if f = 'EditRedo' then Redo else
+  if f = 'EditDoBegin' then DoBegin else
+  if f = 'EditDoEnd' then AVars.Booleans['Result'] := DoEnd else
   if f = 'EditInvertSelection' then InvertSelection else
   if f = 'EditInvertSelection' then InvertSelection else
   if f = 'EditDeselect' then Deselect else
   if f = 'EditDeselect' then Deselect else
   if f = 'EditCopy' then CopySelection else
   if f = 'EditCopy' then CopySelection else
@@ -548,6 +554,23 @@ begin
   end;
   end;
 end;
 end;
 
 
+procedure TImageActions.DoBegin;
+begin
+  if CurrentTool in[ptMoveSelection,ptRotateSelection] then ChooseTool(ptHand);
+  if ToolManager.ToolProvideCommand(tcFinish) then ToolManager.ToolCommand(tcFinish);
+  Image.DoBegin;
+end;
+
+function TImageActions.DoEnd: boolean;
+var
+  found: boolean;
+begin
+  if CurrentTool in[ptMoveSelection,ptRotateSelection] then ChooseTool(ptHand);
+  if ToolManager.ToolProvideCommand(tcFinish) then ToolManager.ToolCommand(tcFinish);
+  Image.DoEnd(found, result);
+  if not found then raise exception.Create(rsEndWithoutMatchingBegin);
+end;
+
 procedure TImageActions.Import3DObject(AFilenameUTF8: string);
 procedure TImageActions.Import3DObject(AFilenameUTF8: string);
 var image3D: TBGRABitmap;
 var image3D: TBGRABitmap;
 begin
 begin

+ 61 - 2
lazpaint/image/ustatetype.pas

@@ -64,15 +64,19 @@ type
   TComposedImageDifference = class(TCustomImageDifference)
   TComposedImageDifference = class(TCustomImageDifference)
   private
   private
     function GetCount: integer;
     function GetCount: integer;
+    function GetItem(AIndex: integer): TCustomImageDifference;
+    function GetTotalCount: integer;
   protected
   protected
     FDiffs: TImageDifferenceList;
     FDiffs: TImageDifferenceList;
+    FAgglutinate: boolean;
     function GetIsIdentity: boolean; override;
     function GetIsIdentity: boolean; override;
     function GetImageDifferenceKind: TImageDifferenceKind; override;
     function GetImageDifferenceKind: TImageDifferenceKind; override;
     function GetChangingBounds: TRect; override;
     function GetChangingBounds: TRect; override;
     function GetChangingBoundsDefined: boolean; override;
     function GetChangingBoundsDefined: boolean; override;
   public
   public
-    constructor Create;
+    constructor Create(AAgglutinate: boolean = false);
     procedure ReleaseDiffs;
     procedure ReleaseDiffs;
+    procedure StopAgglutinate;
     destructor Destroy; override;
     destructor Destroy; override;
     function TryCompress: boolean; override;
     function TryCompress: boolean; override;
     function UsedMemory: int64; override;
     function UsedMemory: int64; override;
@@ -80,8 +84,15 @@ type
     procedure AddRange(AComposed: TComposedImageDifference);
     procedure AddRange(AComposed: TComposedImageDifference);
     procedure ApplyTo(AState: TState); override;
     procedure ApplyTo(AState: TState); override;
     procedure UnapplyTo(AState: TState); override;
     procedure UnapplyTo(AState: TState); override;
+    procedure Clear;
+    procedure Delete(AIndex: integer);
+    procedure DeleteFrom(AIndex: integer);
+    function GetLast: TCustomImageDifference;
     function ToString: ansistring; override;
     function ToString: ansistring; override;
     property Count: integer read GetCount;
     property Count: integer read GetCount;
+    property TotalCount: integer read GetTotalCount;
+    property Agglutinate: boolean read FAgglutinate;
+    property Item[AIndex: integer]: TCustomImageDifference read GetItem; default;
   end;
   end;
 
 
 {*********** Layer info *************}
 {*********** Layer info *************}
@@ -400,10 +411,28 @@ begin
   result := FDiffs.Count;
   result := FDiffs.Count;
 end;
 end;
 
 
+function TComposedImageDifference.GetItem(AIndex: integer): TCustomImageDifference;
+begin
+  result := FDiffs[AIndex];
+end;
+
+function TComposedImageDifference.GetTotalCount: integer;
+var
+  i: Integer;
+begin
+  result := 0;
+  for i := 0 to FDiffs.Count-1 do
+    if FDiffs[i] is TComposedImageDifference then
+      inc(result, TComposedImageDifference(FDiffs[i]).TotalCount)
+    else
+      inc(result);
+end;
+
 function TComposedImageDifference.GetIsIdentity: boolean;
 function TComposedImageDifference.GetIsIdentity: boolean;
 var
 var
   i: Integer;
   i: Integer;
 begin
 begin
+  if FAgglutinate then exit(false);
   for i := 0 to FDiffs.Count-1 do
   for i := 0 to FDiffs.Count-1 do
     if not FDiffs[i].GetIsIdentity then exit(false);
     if not FDiffs[i].GetIsIdentity then exit(false);
   exit(true);
   exit(true);
@@ -452,9 +481,10 @@ begin
   exit(true);
   exit(true);
 end;
 end;
 
 
-constructor TComposedImageDifference.Create;
+constructor TComposedImageDifference.Create(AAgglutinate: boolean);
 begin
 begin
   FDiffs := TImageDifferenceList.Create;
   FDiffs := TImageDifferenceList.Create;
+  FAgglutinate:= AAgglutinate;
 end;
 end;
 
 
 procedure TComposedImageDifference.ReleaseDiffs;
 procedure TComposedImageDifference.ReleaseDiffs;
@@ -464,6 +494,11 @@ begin
   FDiffs.FreeObjects:= true;
   FDiffs.FreeObjects:= true;
 end;
 end;
 
 
+procedure TComposedImageDifference.StopAgglutinate;
+begin
+  FAgglutinate:= false;
+end;
+
 destructor TComposedImageDifference.Destroy;
 destructor TComposedImageDifference.Destroy;
 begin
 begin
   FDiffs.Free;
   FDiffs.Free;
@@ -517,6 +552,30 @@ begin
     FDiffs[i].UnapplyTo(AState);
     FDiffs[i].UnapplyTo(AState);
 end;
 end;
 
 
+procedure TComposedImageDifference.Clear;
+begin
+  FDiffs.Clear;
+end;
+
+procedure TComposedImageDifference.Delete(AIndex: integer);
+begin
+  FDiffs.Delete(AIndex);
+end;
+
+procedure TComposedImageDifference.DeleteFrom(AIndex: integer);
+var
+  i: Integer;
+begin
+  for i := Count-1 downto AIndex do
+    Delete(i);
+end;
+
+function TComposedImageDifference.GetLast: TCustomImageDifference;
+begin
+  if Count = 0 then result := nil
+  else result := FDiffs[Count-1];
+end;
+
 function TComposedImageDifference.ToString: ansistring;
 function TComposedImageDifference.ToString: ansistring;
 var
 var
   i: Integer;
   i: Integer;

+ 5 - 0
lazpaint/lazpaintinstance.pas

@@ -1334,6 +1334,7 @@ var
   p: TPythonScript;
   p: TPythonScript;
   fError: TForm;
   fError: TForm;
   memo: TMemo;
   memo: TMemo;
+  doFound, somethingDone: boolean;
 begin
 begin
   p := nil;
   p := nil;
   try
   try
@@ -1370,6 +1371,10 @@ begin
     end;
     end;
   end;
   end;
   p.Free;
   p.Free;
+  //ensure we are out of any do group
+  repeat
+    Image.DoEnd(doFound, somethingDone);
+  until not doFound;
 end;
 end;
 
 
 procedure TLazPaintInstance.ColorFromFChooseColor;
 procedure TLazPaintInstance.ColorFromFChooseColor;

+ 2 - 0
lazpaint/uresourcestrings.pas

@@ -178,6 +178,7 @@ resourcestring
   rsNoAndProceedToNext='Do not save and open another file';
   rsNoAndProceedToNext='Do not save and open another file';
   rsInformation='Information';
   rsInformation='Information';
   rsError='Error';
   rsError='Error';
+  rsEndWithoutMatchingBegin = 'End without matching begin';
   rsThereAreNoCheckedItems='There are no checked items. Check some items or add some new ones.';
   rsThereAreNoCheckedItems='There are no checked items. Check some items or add some new ones.';
   rsThereIsNoFileNameGivenForThisFileUseSaveAs='There is no file name given for this file. Use "Save as..." from the main menu.';
   rsThereIsNoFileNameGivenForThisFileUseSaveAs='There is no file name given for this file. Use "Save as..." from the main menu.';
   rsFileNotFound='File not found!';
   rsFileNotFound='File not found!';
@@ -187,6 +188,7 @@ resourcestring
   rsOpenFilesAsLayers='Open files as layers in a single image';
   rsOpenFilesAsLayers='Open files as layers in a single image';
   rsTooManyLayers='Too many layers';
   rsTooManyLayers='Too many layers';
   rsTooManyShapesInLayer='Too many shapes in layer';
   rsTooManyShapesInLayer='Too many shapes in layer';
+  rsTooManyActions='Too many actions';
   rsCannotDrawShapeOnSVGLayer='Cannot draw shape on SVG layer';
   rsCannotDrawShapeOnSVGLayer='Cannot draw shape on SVG layer';
   rsAddToImageList='Add files to the image processing list';
   rsAddToImageList='Add files to the image processing list';
   rsOpenFirstFileOnly='Open the first file only';
   rsOpenFirstFileOnly='Open the first file only';

+ 52 - 31
scripts/layer_shadow.py

@@ -14,69 +14,91 @@ if layer.is_empty():
 
 
 ############ image processing
 ############ image processing
 
 
+MAX_RADIUS = 100
+MAX_OFFSET = 100
+
+image.do_begin()
 chosen_radius = 10
 chosen_radius = 10
 chosen_offset = (10, 10)
 chosen_offset = (10, 10)
 
 
-#create shadow layer
-shadow_layer_done = False
-def undo_shadow_layer():
-    global shadow_layer_done
-    if shadow_layer_done:
-        image.undo()
-        image.undo()
-        image.undo()
-        image.undo()
-        shadow_layer_done = False 
-
 def create_shadow_layer():
 def create_shadow_layer():
-    global shadow_layer_done
+    image.do_begin()
     layer.duplicate()
     layer.duplicate()
     shadow_index = image.get_layer_index()
     shadow_index = image.get_layer_index()
     image.move_layer_index(shadow_index, shadow_index-1)
     image.move_layer_index(shadow_index, shadow_index-1)
     colors.lightness(shift=-1)
     colors.lightness(shift=-1)
     opacity = layer.get_opacity() 
     opacity = layer.get_opacity() 
-    layer.set_opacity(opacity*2/3)
-    shadow_layer_done = True
+    layer.set_opacity(opacity*2/3)    
+    image.do_end()
 
 
 blur_done = False
 blur_done = False
-def undo_blur():
-    global blur_done
-    if blur_done:
+offset_done = False
+
+def apply_blur():
+    global blur_done, offset_done
+    if offset_done:
         image.undo()
         image.undo()
+        offset_done = False
+    if blur_done:
         image.undo()
         image.undo()
         blur_done = False
         blur_done = False
-
-def apply_blur():
-    global blur_done
-    undo_blur()
+    image.do_begin() 
     filters.blur(radius=chosen_radius)
     filters.blur(radius=chosen_radius)
+    blur_done = image.do_end()
+    apply_offset()
+
+def apply_offset():
+    global offset_done
+    if offset_done:
+        image.undo()
+        offset_done = False
+    image.do_begin()
     tools.choose(tools.MOVE_LAYER)
     tools.choose(tools.MOVE_LAYER)
     tools.mouse([(0,0), chosen_offset])
     tools.mouse([(0,0), chosen_offset])
-    blur_done = True
+    offset_done = image.do_end()
 
 
 ######## interface
 ######## interface
 
 
 def button_ok_click():
 def button_ok_click():
+    image.do_end()
     exit()
     exit()
 
 
 def button_cancel_click():
 def button_cancel_click():
-    undo_blur()
-    undo_shadow_layer()
+    if image.do_end():
+        image.undo()
     exit()
     exit()
 
 
 scale_radius_update_job = None
 scale_radius_update_job = None
 
 
 def scale_radius_update_do():
 def scale_radius_update_do():
     global scale_radius_update_job, chosen_radius, scale_radius
     global scale_radius_update_job, chosen_radius, scale_radius
-    chosen_radius = scale_radius.get()
-    apply_blur()
+    new_radius = scale_radius.get() 
+    if new_radius != chosen_radius:
+        chosen_radius = new_radius
+        apply_blur()
     scale_radius_update_job = None    
     scale_radius_update_job = None    
 
 
 def scale_radius_update(event):
 def scale_radius_update(event):
     global window, scale_radius_update_job
     global window, scale_radius_update_job
     if scale_radius_update_job:
     if scale_radius_update_job:
         window.after_cancel(scale_radius_update_job)
         window.after_cancel(scale_radius_update_job)
-    scale_radius_update_job = window.after(500, scale_radius_update_do)    
+    scale_radius_update_job = window.after(500, scale_radius_update_do)
+
+scale_offset_update_job = None
+
+def scale_offset_update_do():
+    global chosen_offset 
+    new_offset = (scale_offset_x.get(), scale_offset_y.get())
+    if new_offset != chosen_offset:
+        chosen_offset = new_offset
+        apply_offset()
+    scale_offset_update_job = None
+
+def scale_offset_update(event):   
+    global window, scale_offset_update_job
+    if scale_offset_update_job:
+        window.after_cancel(scale_offset_update_job)
+    scale_offset_update_job = window.after(100, scale_offset_update_do)
 
 
 window = Tk()
 window = Tk()
 window.title("Layer shadow")
 window.title("Layer shadow")
@@ -87,16 +109,16 @@ frame.pack()
 
 
 label_radius = Label(frame, text="Radius:")
 label_radius = Label(frame, text="Radius:")
 label_radius.grid(column=0, row=0)
 label_radius.grid(column=0, row=0)
-scale_radius = Scale(frame, from_=1, to=100, orient=HORIZONTAL, command=scale_radius_update)
+scale_radius = Scale(frame, from_=0, to=MAX_RADIUS, orient=HORIZONTAL, command=scale_radius_update)
 scale_radius.grid(column=1, row=0, columnspan=2, sticky=W+E, padx=10)
 scale_radius.grid(column=1, row=0, columnspan=2, sticky=W+E, padx=10)
 scale_radius.set(chosen_radius)
 scale_radius.set(chosen_radius)
 
 
 label_offset = Label(frame, text="Offset:")
 label_offset = Label(frame, text="Offset:")
 label_offset.grid(column=0, row=1)
 label_offset.grid(column=0, row=1)
-scale_offset_x = Scale(frame, from_=-100, to=100, orient=HORIZONTAL)
+scale_offset_x = Scale(frame, from_=-MAX_OFFSET, to=MAX_OFFSET, orient=HORIZONTAL, command=scale_offset_update)
 scale_offset_x.grid(column=1, row=1, sticky=W+E, padx=10)
 scale_offset_x.grid(column=1, row=1, sticky=W+E, padx=10)
 scale_offset_x.set(chosen_offset[0])
 scale_offset_x.set(chosen_offset[0])
-scale_offset_y = Scale(frame, from_=-100, to=100, orient=HORIZONTAL)
+scale_offset_y = Scale(frame, from_=-MAX_OFFSET, to=MAX_OFFSET, orient=HORIZONTAL, command=scale_offset_update)
 scale_offset_y.grid(column=2, row=1, sticky=W+E, padx=10)
 scale_offset_y.grid(column=2, row=1, sticky=W+E, padx=10)
 scale_offset_y.set(chosen_offset[1])
 scale_offset_y.set(chosen_offset[1])
 
 
@@ -120,5 +142,4 @@ screen_width = window.winfo_screenwidth()
 window.geometry('+%d+0' % (int((screen_width - window_width) / 2)))
 window.geometry('+%d+0' % (int((screen_width - window_width) / 2)))
 
 
 window.mainloop()
 window.mainloop()
-
 button_cancel_click()
 button_cancel_click()

+ 8 - 0
scripts/lazpaint/image.py

@@ -124,3 +124,11 @@ def undo():
 def redo():
 def redo():
   command.send("EditRedo")
   command.send("EditRedo")
 
 
+# starts a series of actions (undoable with only one call to "undo")
+def do_begin():
+  command.send("EditDoBegin")
+
+# returns True if some action was done within the series of actions
+def do_end():
+  return command.send("EditDoEnd?")
+