From baabe96213b6d05dee32139da0deeaa43748c4d1 Mon Sep 17 00:00:00 2001 From: mattias Date: Tue, 18 Feb 2014 21:46:36 +0000 Subject: [PATCH] IDE: undo for designer, patch #22584 git-svn-id: trunk@44156 - --- components/ideintf/componenteditors.pas | 13 + components/ideintf/objectinspector.pp | 81 ++++++ designer/controlselection.pp | 127 +++++++-- designer/designer.pp | 345 +++++++++++++++++++++++- ide/main.pp | 23 +- 5 files changed, 558 insertions(+), 31 deletions(-) diff --git a/components/ideintf/componenteditors.pas b/components/ideintf/componenteditors.pas index be60355a80..cc30de6c49 100644 --- a/components/ideintf/componenteditors.pas +++ b/components/ideintf/componenteditors.pas @@ -37,6 +37,9 @@ type cedhtModified ); + TUndoOpType = (uopNone = 0, uopAdd, uopChange, uopDel); + TUndoCompState = (ucsNone = 0, ucsStartChange, ucsSaveChange); + TComponentEditorDesigner = class(TIDesigner) private FChangeStamp: int64; @@ -48,6 +51,8 @@ type procedure AddHandler(HookType: TComponentEditorDesignerHookType; const Handler: TMethod); procedure RemoveHandler(HookType: TComponentEditorDesignerHookType; const Handler: TMethod); public + FUndoState: TUndoCompState; + destructor Destroy; override; procedure Modified; override; function CopySelection: boolean; virtual; abstract; @@ -63,6 +68,14 @@ type function InvokeComponentEditor(AComponent: TComponent; MenuIndex: integer): boolean; virtual; abstract; + function CanUndo: Boolean; virtual; abstract; + function CanRedo: Boolean; virtual; abstract; + function Undo: Boolean; virtual; abstract; + function Redo: Boolean; virtual; abstract; + function AddUndoAction(const AComp: TComponent; AOpType: TUndoOpType; + IsSetNewId: boolean; AFieldName: string; const AOldVal, ANewVal: variant): boolean; virtual; abstract; + function IsUndoNotLock: boolean; virtual; abstract; + procedure DrawDesignerItems(OnlyIfNeeded: boolean); virtual; abstract; function CreateUniqueComponentName(const AClassName: string ): string; virtual; abstract; diff --git a/components/ideintf/objectinspector.pp b/components/ideintf/objectinspector.pp index 57af125530..75c371162b 100644 --- a/components/ideintf/objectinspector.pp +++ b/components/ideintf/objectinspector.pp @@ -1295,6 +1295,16 @@ var NewValue: string; OldExpanded: boolean; OldChangeStep: integer; + RootDesigner: TIDesigner; + CurrComp: TComponent; + i, j, saveIndex, tmpInt: integer; + newVal, tmpStr, newValAsInt: string; + oldVal: array of string; + isExcept, isIntValInStr: boolean; + parRow, tmpRow: TOIPropertyGridRow; + CompEditDsg: TComponentEditorDesigner; + prpInfo: PPropInfo; + begin //debugln(['TOICustomPropertyGrid.SetRowValue A ',dbgs(FStates*[pgsChangingItemIndex,pgsApplyingValue]<>[]),' FItemIndex=',dbgs(FItemIndex),' CanEditRowValue=',CanEditRowValue]); if not CanEditRowValue(CheckFocus) or Rows[FItemIndex].IsReadOnly then exit; @@ -1308,6 +1318,49 @@ begin //DebugLn(['TOICustomPropertyGrid.SetRowValue Old="',CurRow.Editor.GetVisualValue,'" New="',NewValue,'"']); if CurRow.Editor.GetVisualValue=NewValue then exit; + RootDesigner := FindRootDesigner(FCurrentEditorLookupRoot); + if (RootDesigner is TComponentEditorDesigner) and + not (RootDesigner as TComponentEditorDesigner).IsUndoNotLock then Exit; + CompEditDsg := (RootDesigner as TComponentEditorDesigner); + + + isExcept := false; + saveIndex := FItemIndex; + SetLength(oldVal, Selection.Count); + for i := 0 to Selection.Count - 1 do + begin + CurrComp := CompEditDsg.Form.FindComponent(Selection.Items[i].GetNamePath); + + while CurRow.Parent <> nil do + CurRow := CurRow.Parent; + + prpInfo := GetPropInfo(TObject(CurrComp), CurRow.Name); + if not Assigned(prpInfo) then + ShowMessage('error: propInfo = nil') + else + case prpInfo^.PropType^.Kind of + tkInteger, tkInt64: + oldVal[i] := IntToStr(GetOrdProp(TObject(CurrComp), prpInfo)); + tkChar, tkWChar, tkUChar: + oldVal[i] := Char(GetOrdProp(TObject(CurrComp), prpInfo)); + tkEnumeration: + oldVal[i] := GetEnumName(prpInfo^.PropType, GetOrdProp(TObject(CurrComp), CurRow.Name)); + tkFloat: + oldVal[i] := FloatToStr(GetFloatProp(TObject(CurrComp), prpInfo)); + tkBool: + oldVal[i] := BoolToStr(Boolean(GetOrdProp(TObject(CurrComp), prpInfo)), 'True', 'False'); + tkString, tkLString, tkAString, tkUString, tkWString: + oldVal[i] := GetStrProp(TObject(CurrComp), prpInfo); + tkSet: + oldVal[i] := GetSetProp(TObject(CurrComp), CurRow.Name); + tkVariant: + oldVal[i] := GetVariantProp(TObject(CurrComp), prpInfo); + end; + end; + FItemIndex := saveIndex; + CurRow := Rows[FItemIndex]; + + OldChangeStep:=fChangeStep; Include(FStates,pgsApplyingValue); try @@ -1321,6 +1374,7 @@ begin except on E: Exception do begin MessageDlg(oisError, E.Message, mtError, [mbOk], 0); + isExcept := true; end; end; {$ENDIF} @@ -1329,6 +1383,33 @@ begin exit; end; + if not isExcept then + begin + if Assigned(prpInfo) and (prpInfo^.PropType^.Kind = tkSet) then + newVal := GetSetProp(TObject(CurrComp), CurRow.Parent.Name) + else if Assigned(prpInfo) and (prpInfo^.PropType^.Kind = tkInteger) and not TryStrToInt(NewValue, i) then + newVal := IntToStr(CurRow.Editor.GetOrdValue) + else + newVal := NewValue; + + for i := 0 to Selection.Count - 1 do + begin + CurRow := Rows[saveIndex]; + if CompEditDsg.Form.Name = Selection.Items[i].GetNamePath then + CurrComp := CompEditDsg.Form + else + CurrComp := CompEditDsg.Form.FindComponent(Selection.Items[i].GetNamePath); + if CurrComp <> nil then + begin + while CurRow.Parent <> nil do + CurRow := CurRow.Parent; + + CompEditDsg.AddUndoAction(CurrComp, uopChange, i = 0, + curRow.Name, oldVal[i], newVal); + end; + end; + end; + // set value in edit control SetCurrentEditValue(CurRow.Editor.GetVisualValue); diff --git a/designer/controlselection.pp b/designer/controlselection.pp index 7dbb16a87f..25ab826a92 100644 --- a/designer/controlselection.pp +++ b/designer/controlselection.pp @@ -42,6 +42,8 @@ uses FormEditingIntf, NonControlDesigner, DesignerProcs; type + TArrSize = array of array [0 .. 3] of integer; + TControlSelection = class; TGrabber = class; @@ -340,6 +342,7 @@ type procedure SetRubberbandType(const AValue: TRubberbandType); procedure SetSnapping(const AValue: boolean); procedure SetVisible(const AValue: Boolean); + procedure GetCompSize(var arr: TArrSize); protected procedure AdjustGrabbers; procedure InvalidateGrabbers; @@ -373,6 +376,8 @@ type procedure FindNearestTopGuideLine(var NearestInt: TNearestInt); procedure ImproveNearestInt(var NearestInt: TNearestInt; Candidate: integer); public + arrOldSize, arrNewSize: TArrSize; + constructor Create; reintroduce; destructor Destroy; override; procedure OnIdle(Sender: TObject; var {%H-}Done: Boolean); @@ -408,8 +413,8 @@ type // resizing, moving, aligning, mirroring, ... function IsResizing: boolean; - procedure BeginResizing; - procedure EndResizing(ApplyUserBounds: boolean); + procedure BeginResizing(IsStartUndo: boolean); + procedure EndResizing(ApplyUserBounds, ActionIsProcess: boolean); procedure SaveBounds; procedure UpdateBounds; procedure RestoreBounds; @@ -512,6 +517,8 @@ var TheControlSelection: TControlSelection; implementation +uses + ComponentEditors; const GRAB_CURSOR: array[TGrabIndex] of TCursor = ( @@ -961,13 +968,61 @@ begin end; end; -procedure TControlSelection.BeginResizing; +procedure TControlSelection.BeginResizing(IsStartUndo: boolean); +var + CompEditDesig: TComponentEditorDesigner; begin if FResizeLockCount=0 then BeginUpdate; inc(FResizeLockCount); + + SetLength(arrOldSize, Count); + GetCompSize(arrOldSize); + + CompEditDesig := FindRootDesigner(Items[0].Persistent) as TComponentEditorDesigner; + if (Count > 0) and (CompEditDesig.FUndoState = ucsNone) and IsStartUndo then + CompEditDesig.FUndoState := ucsStartChange; end; -procedure TControlSelection.EndResizing(ApplyUserBounds: boolean); +procedure TControlSelection.EndResizing(ApplyUserBounds, ActionIsProcess: boolean); +var + IsActionsBegin: boolean; + + function SaveAct(AIndex: integer): boolean; + var + CurrComp: TComponent; + RootDesigner: TIDesigner; + begin + Result := false; + RootDesigner := FindRootDesigner(Items[AIndex].Persistent); + if ((RootDesigner as TComponentEditorDesigner).FUndoState = ucsStartChange) + and Items[AIndex].IsTComponent then + begin + if SelectionForm.Name = TComponent(Items[AIndex].Persistent).Name then + CurrComp := SelectionForm + else + CurrComp := SelectionForm.FindComponent(TComponent(Items[AIndex].Persistent).Name); + Result := (CurrComp <> nil) and (RootDesigner is TComponentEditorDesigner) and + ((arrOldSize[AIndex, 0] <> arrNewSize[AIndex, 0]) or + (arrOldSize[AIndex, 1] <> arrNewSize[AIndex, 1]) or + (arrOldSize[AIndex, 2] <> arrNewSize[AIndex, 2]) or + (arrOldSize[AIndex, 3] <> arrNewSize[AIndex, 3])); + if Result then + with (RootDesigner as TComponentEditorDesigner) do + begin + AddUndoAction(CurrComp, uopChange, not(IsActionsBegin), 'Top', + arrOldSize[AIndex, 0], arrNewSize[AIndex, 0]); + AddUndoAction(CurrComp, uopChange, false, 'Left', + arrOldSize[AIndex, 1], arrNewSize[AIndex, 1]); + AddUndoAction(CurrComp, uopChange, false, 'Height', + arrOldSize[AIndex, 2], arrNewSize[AIndex, 2]); + AddUndoAction(CurrComp, uopChange, false, 'Width', + arrOldSize[AIndex, 3], arrNewSize[AIndex, 3]); + end; + end; + end; + +var + i: integer; begin if FResizeLockCount<=0 then begin DebugLn('WARNING: TControlSelection.EndResizing FResizeLockCount=',IntToStr(FResizeLockCount)); @@ -975,6 +1030,20 @@ begin end; if FResizeLockCount=1 then if ApplyUserBounds then DoApplyUserBounds; + + IsActionsBegin := false; + SetLength(arrNewSize, Count); + GetCompSize(arrNewSize); + for i := 0 to Count - 1 do + IsActionsBegin := SaveAct(i) or IsActionsBegin; + if ActionIsProcess then + begin + if IsActionsBegin then + (FindRootDesigner(Items[0].Persistent) as TComponentEditorDesigner).FUndoState := ucsSaveChange + end + else + (FindRootDesigner(Items[0].Persistent) as TComponentEditorDesigner).FUndoState := ucsNone; + dec(FResizeLockCount); if FResizeLockCount>0 then exit; EndUpdate; @@ -1949,6 +2018,20 @@ begin Exclude(FStates,cssVisible); end; +procedure TControlSelection.GetCompSize(var arr: TArrSize); +var + i: integer; +begin + for i := 0 to Count - 1 do + if Items[i].IsTComponent then + begin + arr[i, 0] := Items[i].Top; + arr[i, 1] := Items[i].Left; + arr[i, 2] := Items[i].Height; + arr[i, 3] := Items[i].Width; + end; +end; + function TControlSelection.GetItems(Index:integer):TSelectedControl; begin Result:=TSelectedControl(FControls[Index]); @@ -2186,10 +2269,10 @@ begin if (NewLeft <> FLeft) or (NewTop <> FTop) then begin Result := True; - BeginResizing; + BeginResizing(false); FLeft := NewLeft; FTop := NewTop; - EndResizing(True); + EndResizing(True, False); end; end; @@ -2211,14 +2294,14 @@ begin if (NewLeft <> FLeft) or (NewTop <> FTop) then begin Result := True; - BeginResizing; + BeginResizing(True); FLeft := NewLeft; FTop := NewTop; {$IFDEF VerboseDesigner} DebugLn('[TControlSelection.MoveSelectionWithSnapping] B ', ' Bounds='+dbgs(FLeft)+','+dbgs(FTop)+','+dbgs(FWidth)+','+dbgs(FHeight)); {$ENDIF} - EndResizing(True); + EndResizing(True, True); end; end; @@ -2241,7 +2324,7 @@ begin if [gpLeft,gpRight] * GrabberPos = [] then dx:=0; if (dx=0) and (dy=0) then exit; - BeginResizing; + BeginResizing(true); if gpLeft in GrabberPos then begin FLeft:=FLeft+dx; FWidth:=FWidth-dx; @@ -2256,19 +2339,19 @@ begin else if gpBottom in GrabberPos then begin FHeight:=FHeight+dy; end; - EndResizing(true); + EndResizing(true, true); end; procedure TControlSelection.SetBounds(NewLeft, NewTop, NewWidth, NewHeight: integer); begin if (Count=0) or (IsResizing) then exit; - BeginResizing; + BeginResizing(false); FLeft:=NewLeft; FTop:=NewTop; FWidth:=NewWidth; FHeight:=NewHeight; - EndResizing(true); + EndResizing(true, false); end; function TControlSelection.GrabberAtPos(X,Y:integer):TGrabber; @@ -2816,7 +2899,7 @@ begin if (Items[0].IsTopLvl) or ((HorizAlignment=csaNone) and (VertAlignment=csaNone)) then exit; - BeginResizing; + BeginResizing(true); // initializing ALeft:=Items[0].Left; @@ -2937,7 +3020,7 @@ begin end; end; - EndResizing(false); + EndResizing(false, false); end; procedure TControlSelection.MirrorHorizontal; @@ -2945,7 +3028,7 @@ var i, ALeft, ARight, Middle, NewLeft: integer; begin if (FControls.Count=0) or (Items[0].IsTopLvl) then exit; - BeginResizing; + BeginResizing(true); // initializing ALeft:=Items[0].Left; @@ -2966,7 +3049,7 @@ begin end; UpdateBounds; - EndResizing(false); + EndResizing(false, false); end; procedure TControlSelection.MirrorVertical; @@ -2974,7 +3057,7 @@ var i, ATop, ABottom, Middle, NewTop: integer; begin if (FControls.Count=0) or (Items[0].IsTopLvl) then exit; - BeginResizing; + BeginResizing(true); // initializing ATop:=Items[0].Top; @@ -2995,7 +3078,7 @@ begin end; UpdateBounds; - EndResizing(false); + EndResizing(false, false); end; procedure TControlSelection.SizeComponents( @@ -3004,7 +3087,7 @@ procedure TControlSelection.SizeComponents( var i: integer; begin if (FControls.Count=0) or (Items[0].IsTopLvl) then exit; - BeginResizing; + BeginResizing(true); // initialize case HorizSizing of @@ -3041,14 +3124,14 @@ begin end; end; - EndResizing(false); + EndResizing(false, false); end; procedure TControlSelection.ScaleComponents(Percent: integer); var i: integer; begin if (FControls.Count=0) then exit; - BeginResizing; + BeginResizing(true); if Percent<1 then Percent:=1; if Percent>1000 then Percent:=1000; @@ -3064,7 +3147,7 @@ begin end; end; - EndResizing(false); + EndResizing(false, false); end; function TControlSelection.CheckForLCLChanges(Update: boolean): boolean; diff --git a/designer/designer.pp b/designer/designer.pp index 281b96ed22..b689db4870 100644 --- a/designer/designer.pp +++ b/designer/designer.pp @@ -38,9 +38,10 @@ interface uses // FCL + LCL - Types, Classes, SysUtils, Math, LCLProc, LCLType, LResources, LCLIntf, LMessages, - InterfaceBase, Forms, Controls, GraphType, Graphics, Dialogs, ExtCtrls, Menus, - ClipBrd, TypInfo, contnrs, + Types, Classes, Math, SysUtils, contnrs, variants, TypInfo, + LCLProc, LCLType, LResources, LCLIntf, LMessages, InterfaceBase, + Forms, Controls, GraphType, Graphics, Dialogs, ExtCtrls, Menus, + ClipBrd, // IDEIntf IDEDialogs, PropEdits, PropEditUtils, ComponentEditors, MenuIntf, IDEImagesIntf, FormEditingIntf, ComponentReg, @@ -81,6 +82,17 @@ type ); TDesignerFlags = set of TDesignerFlag; + TUndoList = record + obj: string; + fieldName: string; + propInfo: TPropInfo; + oldVal, newVal: Variant; + compName, parentName: TComponentName; + opType: TUndoOpType; + isValid: Boolean; + id: int64; + end; + { TDesigner } TDesigner = class(TComponentEditorDesigner) @@ -117,6 +129,10 @@ type FShiftState: TShiftState; FTheFormEditor: TCustomFormEditor; FPopupMenuComponentEditor: TBaseComponentEditor; + FUndoList: array of TUndoList; + FUndoCurr: integer; + FUndoLock: integer; + FUndoActId: int64; //hint stuff FHintTimer: TTimer; @@ -197,6 +213,12 @@ type ): boolean; function DoInsertFromStream(s: TStream; PasteParent: TWinControl; PasteFlags: TComponentPasteSelectionFlags): Boolean; + + function DoUndo: Boolean; + function DoRedo: Boolean; + procedure SetNewVal(IsActUndo: boolean); + procedure SetNextUndoActId; + procedure DoShowAnchorEditor; procedure DoShowTabOrderEditor; procedure DoShowChangeClassDialog; @@ -276,6 +298,15 @@ type procedure DoProcessCommand(Sender: TObject; var Command: word; var Handled: boolean); + function CanUndo: Boolean; override; + function CanRedo: Boolean; override; + function Undo: Boolean; override; + function Redo: Boolean; override; + function AddUndoAction(const AComp: TComponent; AOpType: TUndoOpType; + IsSetNewId: boolean; AFieldName: string; const AOldVal, ANewVal: variant): boolean; override; + function IsUndoNotLock: boolean; override; + procedure ClearUndoItem(AIndex: Integer); + function NonVisualComponentLeftTop(AComponent: TComponent): TPoint; function NonVisualComponentAtPos(X, Y: integer): TComponent; procedure MoveNonVisualComponentIntoForm(AComponent: TComponent); @@ -594,6 +625,8 @@ end; constructor TDesigner.Create(TheDesignerForm: TCustomForm; AControlSelection: TControlSelection); +var + i: integer; begin inherited Create; //debugln(['TDesigner.Create Self=',dbgs(Pointer(Self)),' TheDesignerForm=',DbgSName(TheDesignerForm)]); @@ -627,6 +660,14 @@ begin DeletingPersistent:=TList.Create; IgnoreDeletingPersistent:=TList.Create; FPopupMenuComponentEditor := nil; + + SetLength(FUndoList, 64); + for i := Low(FUndoList) to High(FUndoList) do + ClearUndoItem(i); + FUndoCurr := Low(FUndoList); + FUndoLock := 0; + FUndoState := ucsNone; + SetNextUndoActId; end; procedure TDesigner.PrepareFreeDesigner(AFreeComponent: boolean); @@ -1192,6 +1233,8 @@ begin // finish adding component NotifyPersistentAdded(NewComponent); Modified; + // add action in undo list + AddUndoAction(NewComponent, uopAdd, i = 0, 'Name', '', NewComponent.Name); end; if NewSelection.Count>0 then @@ -1209,6 +1252,154 @@ begin Result:=true; end; +function TDesigner.DoUndo: Boolean; +var currId: int64; +begin + repeat + Result := CanUndo; + if not Result then Exit; + Dec(FUndoCurr); + currId := FUndoList[FUndoCurr].id; + SetNewVal(true); + until currId <> FUndoList[FUndoCurr - 1].id; +end; + +function TDesigner.DoRedo: Boolean; +var currId: int64; +begin + repeat + Result := CanRedo; + if not Result then Exit; + SetNewVal(false); + currId := FUndoList[FUndoCurr].id; + Inc(FUndoCurr); + until currId <> FUndoList[FUndoCurr].id; +end; + +procedure TDesigner.SetNewVal(IsActUndo: boolean); + + procedure SetPropVal(AVal: variant); + var + tmpStr, str: string; + tmpCompName: TComponentName; + tmpObj: TObject; + tmpInt: integer; + begin + tmpCompName := FUndoList[FUndoCurr].compName; + if FUndoList[FUndoCurr].fieldName = 'Name' then + begin + if IsActUndo then + tmpCompName := FUndoList[FUndoCurr].newVal + else + tmpCompName := FUndoList[FUndoCurr].oldVal; + end; + + if FForm.Name <> tmpCompName then + tmpObj := TObject(FForm.FindComponent(tmpCompName)) + else + tmpObj := TObject(FForm); + + if VarIsError(AVal) or VarIsEmpty(AVal) or VarIsNull(AVal) then + ShowMessage('error: invalid var type'); + tmpStr := VarToStr(AVal); + + with FUndoList[FUndoCurr] do + case propInfo.propType^.Kind of + tkInteger, tkInt64: + begin + if (propInfo.propType^.Name = 'TColor') or + (propInfo.propType^.Name = 'TGraphicsColor') then + SetOrdProp(tmpObj, fieldName, StringToColor(tmpStr)) + else if propInfo.propType^.Name = 'TCursor' then + SetOrdProp(tmpObj, fieldName, StringToCursor(tmpStr)) + else + SetOrdProp(tmpObj, fieldName, StrToInt(tmpStr)); + end; + tkChar, tkWChar, tkUChar: + begin + if Length(tmpStr) = 1 then + SetOrdProp(tmpObj, FUndoList[FUndoCurr].fieldName, Ord(tmpStr[1])) + else if (tmpStr[1] = '#') then + begin + str := Copy(tmpStr, 2, Length(tmpStr) - 1); + if TryStrToInt(str, tmpInt) and (tmpInt >= 0) and (tmpInt <= High(Byte)) then + SetOrdProp(tmpObj, FUndoList[FUndoCurr].fieldName, tmpInt); + end; + end; + tkEnumeration: + SetEnumProp(tmpObj, FUndoList[FUndoCurr].fieldName, tmpStr); + tkFloat: + SetFloatProp(tmpObj, fieldName, StrToFloat(tmpStr)); + tkBool: + SetOrdProp(tmpObj, FUndoList[FUndoCurr].fieldName, Integer(StrToBool(tmpStr))); + tkString, tkLString, tkAString, tkUString, tkWString: + SetStrProp(tmpObj, fieldName, tmpStr); + tkSet: + SetSetProp(tmpObj, FUndoList[FUndoCurr].fieldName, tmpStr); + tkVariant: + SetVariantProp(tmpObj, fieldName, AVal); + else + ShowMessage(Format('error: unknown TTypeKind(%d)', [Integer(propInfo.propType^.Kind)])); + end; + PropertyEditorHook.Modified(tmpObj); + end; + +var + CurTextCompStream: TMemoryStream; + SaveControlSelection: TControlSelection; +begin + if (IsActUndo and (FUndoList[FUndoCurr].opType in [uopAdd])) or + (not IsActUndo and (FUndoList[FUndoCurr].opType in [uopDel])) then + begin + Inc(FUndoLock); + SaveControlSelection := TControlSelection.Create; + try + SaveControlSelection.Assign(ControlSelection); + ControlSelection.Clear; + ControlSelection.Add(FForm.FindComponent(FUndoList[FUndoCurr].compName)); + DeleteSelection; + finally + ControlSelection.Assign(SaveControlSelection); + SaveControlSelection.Free; + Dec(FUndoLock); + end; + end; + + if (IsActUndo and (FUndoList[FUndoCurr].opType in [uopDel])) or + (not IsActUndo and (FUndoList[FUndoCurr].opType in [uopAdd])) then + begin + CurTextCompStream := TMemoryStream.Create; + try + Inc(FUndoLock); + CurTextCompStream.Write(FUndoList[FUndoCurr].obj[1], Length(FUndoList[FUndoCurr].obj)); + CurTextCompStream.Position := 0; + DoInsertFromStream(CurTextCompStream, + TWinControl(FForm.FindChildControl(FUndoList[FUndoCurr].parentName)), []); + finally + CurTextCompStream.Free; + Dec(FUndoLock); + end; + end; + + if FUndoList[FUndoCurr].opType = uopChange then + begin + Inc(FUndoLock); + if IsActUndo then + SetPropVal(FUndoList[FUndoCurr].oldVal) + else + SetPropVal(FUndoList[FUndoCurr].newVal); + Dec(FUndoLock); + end; + + PropertyEditorHook.RefreshPropertyValues; +end; + +procedure TDesigner.SetNextUndoActId; +begin + Randomize; + FUndoActId := Random(High(Int64)); +end; + procedure TDesigner.DoShowAnchorEditor; begin if Assigned(FOnShowAnchorEditor) then @@ -1413,6 +1604,123 @@ begin Handled := True; end; +function TDesigner.CanUndo: Boolean; +begin + Result := Assigned(Form) and (FUndoCurr > Low(FUndoList)) and + (FUndoList[FUndoCurr - 1].isValid) and (FUndoList[FUndoCurr - 1].opType <> uopNone); +end; + +function TDesigner.CanRedo: Boolean; +begin + Result := Assigned(Form) and (FUndoCurr <= High(FUndoList)) and + (FUndoList[FUndoCurr].isValid) and (FUndoList[FUndoCurr].opType <> uopNone); +end; + +function TDesigner.Undo: Boolean; +begin + Result := DoUndo; +end; + +function TDesigner.Redo: Boolean; +begin + Result := DoRedo; +end; + +function TDesigner.AddUndoAction(const AComp: TComponent; AOpType: TUndoOpType; + IsSetNewId: boolean; AFieldName: string; const AOldVal, ANewVal: variant): boolean; + + procedure ShiftUndoList; + var + i: integer; + begin + for i := Low(FUndoList) + 1 to High(FUndoList) do + FUndoList[i - 1] := FUndoList[i]; + Dec(FUndoCurr); + end; + +var + i: integer; + SaveControlSelection: TControlSelection; + AStream: TStringStream; +begin + Result := (FUndoLock = 0); + if not(Result) then Exit; + Inc(FUndoLock); + + if FUndoCurr > High(FUndoList) then + ShiftUndoList; + + i := FUndoCurr; + while (i <= High(FUndoList)) do + begin + ClearUndoItem(i); + Inc(i); + end; + + if IsSetNewId then + SetNextUndoActId; + + if (AOpType in [uopAdd, uopDel]) and (FForm.Name <> AComp.Name) then + begin + SaveControlSelection := TControlSelection.Create; + try + SaveControlSelection.Assign(ControlSelection); + AStream := TStringStream.Create(''); + try + ControlSelection.Clear; + ControlSelection.Add(AComp); + CopySelectionToStream(AStream); + FUndoList[FUndoCurr].obj := AStream.DataString; + finally + AStream.Free; + end; + finally + ControlSelection.Assign(SaveControlSelection); + SaveControlSelection.Free; + end; + end; + + with FUndoList[FUndoCurr] do + begin + oldVal := AOldVal; + newVal := ANewVal; + fieldName := AFieldName; + compName := AComp.Name; + if not(AComp.Equals(Form)) and AComp.HasParent then + parentName := AComp.GetParentComponent.Name + else + parentName := ''; + opType := AOpType; + isValid := true; + id := FUndoActId; + propInfo := GetPropInfo(TObject(AComp), AFieldName)^; + end; + Inc(FUndoCurr); + Dec(FUndoLock); +end; + +function TDesigner.IsUndoNotLock: boolean; +begin + Result := FUndoLock = 0; +end; + +procedure TDesigner.ClearUndoItem(AIndex: Integer); +begin + if (AIndex < 0) or (AIndex >= Length(FUndoList)) then Exit; + with FUndoList[AIndex] do + begin + obj := ''; + fieldName := ''; + VarClear(oldVal); + VarClear(newVal); + compName := ''; + parentName := ''; + opType := uopNone; + isValid := false; + id := 0; + end; +end; + function TDesigner.NonVisualComponentLeftTop(AComponent: TComponent): TPoint; var ParentForm: TPoint; @@ -1976,6 +2284,7 @@ var DebugLn('NEW COMPONENT ADDED: Form.ComponentCount=',DbgS(Form.ComponentCount), ' NewComponent.Owner.Name=',NewComponent.Owner.Name); {$ENDIF} + AddUndoAction(NewComponent, uopAdd, true, 'Name', '', NewComponent.Name); end; procedure RubberbandSelect; @@ -2047,6 +2356,7 @@ var var Handled: Boolean; + i, j: Integer; begin FHintTimer.Enabled := False; FHintWindow.Visible := False; @@ -2103,6 +2413,26 @@ begin begin if SelectedCompClass = nil then begin + if (FUndoState = ucsSaveChange) and (FUndoCurr > 4) then + begin + j := FUndoCurr - ControlSelection.Count * 4; + for i := 0 to ControlSelection.Count - 1 do + begin + if (FUndoList[j].fieldName = 'Top') + and (FUndoList[j + 1].fieldName = 'Left') + and (FUndoList[j + 2].fieldName = 'Height') + and (FUndoList[j + 3].fieldName = 'Width') then + begin + FUndoList[j].newVal := ControlSelection.Items[i].Top; + FUndoList[j + 1].newVal := ControlSelection.Items[i].Left; + FUndoList[j + 2].newVal := ControlSelection.Items[i].Height; + FUndoList[j + 3].newVal := ControlSelection.Items[i].Width; + end; + j := j + 4; + end; + end; + FUndoState := ucsNone; + // layout mode (selection, moving and resizing) if not (dfHasSized in FFlags) then begin @@ -2471,7 +2801,14 @@ begin exit; end; end; - + + for i := 0 to ControlSelection.Count - 1 do + begin + if not ControlSelection[i].IsTComponent then continue; + AComponent := TComponent(ControlSelection[i].Persistent); + AddUndoAction(AComponent, uopDel, i = 0, 'Name', AComponent.Name, ''); + end; + // mark selected components for deletion for i:=0 to ControlSelection.Count-1 do MarkPersistentForDeletion(ControlSelection[i].Persistent); diff --git a/ide/main.pp b/ide/main.pp index bc92734990..c6857e0f6d 100644 --- a/ide/main.pp +++ b/ide/main.pp @@ -3783,7 +3783,7 @@ var Editable: Boolean; SelAvail: Boolean; SelEditable: Boolean; - SrcEditorActive: Boolean; + SrcEditorActive, DsgEditorActive: Boolean; ActiveDesigner: TComponentEditorDesigner; begin GetCurrentUnit(ASrcEdit, AnUnitInfo); @@ -3791,14 +3791,15 @@ begin SelAvail := Assigned(ASrcEdit) and ASrcEdit.SelectionAvailable; SelEditable := Editable and SelAvail; SrcEditorActive := DisplayState = dsSource; + DsgEditorActive := DisplayState = dsForm; ActiveDesigner := GetActiveDesignerSkipMainBar; with MainIDEBar do begin if Assigned(ActiveDesigner) then begin // activate them when Designer start to support Undo/Redo - itmEditUndo.Enabled := False; - itmEditRedo.Enabled := False; + itmEditUndo.Enabled := DsgEditorActive and ActiveDesigner.CanUndo; {and not ActiveDesigner.ReadOnly} + itmEditRedo.Enabled := DsgEditorActive and ActiveDesigner.CanRedo; {and not ActiveDesigner.ReadOnly} itmEditCut.Enabled := ActiveDesigner.CanCopy; itmEditCopy.Enabled := itmEditCut.Enabled; itmEditPaste.Enabled := ActiveDesigner.CanPaste; @@ -13644,13 +13645,25 @@ begin end; procedure TMainIDE.mnuEditRedoClicked(Sender: TObject); +var + ActiveDesigner: TComponentEditorDesigner; begin - DoSourceEditorCommand(ecRedo); + ActiveDesigner := GetActiveDesignerSkipMainBar; + if Assigned(ActiveDesigner) then + ActiveDesigner.Redo + else + DoSourceEditorCommand(ecRedo); end; procedure TMainIDE.mnuEditUndoClicked(Sender: TObject); +var + ActiveDesigner: TComponentEditorDesigner; begin - DoSourceEditorCommand(ecUndo); + ActiveDesigner := GetActiveDesignerSkipMainBar; + if Assigned(ActiveDesigner) then + ActiveDesigner.Undo + else + DoSourceEditorCommand(ecUndo); end; procedure TMainIDE.mnuEditIndentBlockClicked(Sender: TObject);