[Lazarus] RFC: Selection Editors to extend handling of object inspector selections

Sven Barth pascaldragon at googlemail.com
Sun Nov 10 22:26:11 CET 2019


Hello together!

I've got a RFC for a feature that allows to extend the IDE. It's based 
on something that Delphi supports as well: Selection Editors.
Unlike Component Editors these are supposed to be used to interact with 
a specific selection in the designer and (in Delphi) allow the following 
extensions:
- adding menu items for the context menu for the selection (like 
component editors)
- add additional units when the component is dropped onto the form 
(though I wonder why that isn't part of component editors)
- add or remove properties to/from the object inspector for the selection

My current RFC only implements the last one and also a bit different 
than Delphi does cause they added that functionality only later (see below).

Due to their dependencies Selection Editors live in the PropEdits unit 
and like component and property editors they are registered at an 
approbriate location (be it some Register function or a unit 
initialization). When the object inspector requests the properties it 
also checks all selection editors involved in the selection (included 
the parent classes) and calls the selection editor's FilterProperties 
method if GetAttributes contains seaFilterProperties (Delphi does not 
provide a GetAttributes method, instead it provides FilterProperties 
through an interface that needs to be implemented by a selection editor).
FilterProperties receives both the current selection of the object 
inspector as well as the list of property editors. It is then free to 
add or remove property editors as it sees fit.
The behavior when collecting the property editors is such that multiple 
FilterProperties methods might be called on the same selection, thus it 
might received an already filtered property editor list.

I've attached a patch that implements selection editors 
(selection-editors.patch) as well as an example implementation for a 
component that Lazarus already supports and Delphi provides a selection 
editor for: TFlowPanel (tflowpanel-seledit.patch). For that component 
for each child control a virtual ControlIndex property is exposed which 
allows to easily change the order of the components in the object 
inspector instead of going through some collection editor. Also the Top 
and Left properties are removed as they aren't useful for such 
components. Delphi behaves the same here.

Another example use would be TGridPanel which we don't have yet: it 
allows to implement the Row- and ColumnSpan properties on each child 
control of the panel.

To work correctly I also had to adjust the object inspector a little bit 
(respect-parevertable.patch), cause it is not using only the virtual 
functions provided by TPropertyEditor: namely to respect the 
paRevertable attribute and only initialize and use the undo values if 
the property is deemed revertable. For demonstration purposes I've 
disabled the paRevertable attribute for the ControlIndex property 
editor, cause otherwise there'd be an exception when the object 
inspector tries to read the original value. It would be better if the 
object inspector would only call virtual methods of TPropertyEditor 
however or ones that can be easily faked (like I did by faking a 
TPropInfo entry, though that does not feel really nice either).

Suggestions and criticism are welcome though I can't tell how much time 
I currently can and want to invest to improve this further. It might 
also need some step-by-step improvement of the existing TPropertyEditor 
interface to function smoothly.

Regards,
Sven
-------------- next part --------------
Index: components/ideintf/propedits.pp
===================================================================
--- components/ideintf/propedits.pp	(revision 62212)
+++ components/ideintf/propedits.pp	(working copy)
@@ -23,7 +23,7 @@
 
 uses
   // RTL / FCL
-  Classes, TypInfo, SysUtils, types, RtlConsts, variants, Contnrs, strutils,
+  Classes, TypInfo, SysUtils, types, RtlConsts, variants, Contnrs, strutils, FGL,
   // LCL
   LCLType, LCLIntf, LCLProc, Forms, Controls, GraphType, ButtonPanel, Graphics,
   StdCtrls, Buttons, Menus, ExtCtrls, ComCtrls, Dialogs, EditBtn, Grids, ValEdit,
@@ -416,7 +416,9 @@
   end;
 
   TPropertyEditorClass=class of TPropertyEditor;
-  
+
+  TPropertyEditorList = specialize TFPGObjectList<TPropertyEditor>;
+
 { THiddenPropertyEditor
   A property editor, to hide a published property. If you can't unpublish it,
   hide it. }
@@ -1180,6 +1182,39 @@
   TDateTimeProperty =       TDateTimePropertyEditor;
 
 
+type
+  TSelectionEditorAttribute = (
+    seaFilterProperties
+  );
+  TSelectionEditorAttributes = set of TSelectionEditorAttribute;
+
+  { TBaseSelectionEditor }
+
+  TBaseSelectionEditor = class
+    constructor Create({%H-}ADesigner: TIDesigner; {%H-}AHook: TPropertyEditorHook); virtual;
+    function GetAttributes: TSelectionEditorAttributes; virtual; abstract;
+    procedure FilterProperties(ASelection: TPersistentSelectionList; AProperties: TPropertyEditorList); virtual; abstract;
+  end;
+
+  TSelectionEditorClass = class of TBaseSelectionEditor;
+
+  TSelectionEditorClassList = specialize TFPGList<TSelectionEditorClass>;
+
+  { TSelectionEditor }
+
+  TSelectionEditor = class(TBaseSelectionEditor)
+  private
+    FDesigner: TIDesigner;
+    FHook: TPropertyEditorHook;
+  public
+    constructor Create(ADesigner: TIDesigner; AHook: TPropertyEditorHook); override;
+    function GetAttributes: TSelectionEditorAttributes; override;
+    procedure FilterProperties({%H-}ASelection: TPersistentSelectionList; {%H-}AProperties: TPropertyEditorList); override;
+    property Designer: TIDesigner read FDesigner;
+    property Hook: TPropertyEditorHook read FHook;
+  end;
+
+
 //==============================================================================
 
 { RegisterPropertyEditor
@@ -1258,6 +1293,12 @@
 
 //==============================================================================
 
+procedure RegisterSelectionEditor(AComponentClass: TComponentClass; AEditorClass: TSelectionEditorClass);
+procedure GetSelectionEditorClasses(AComponent: TComponent; AEditorList: TSelectionEditorClassList);
+procedure GetSelectionEditorClasses(ASelection: TPersistentSelectionList; AEditorList: TSelectionEditorClassList);
+
+//==============================================================================
+
 procedure RegisterListPropertyEditor(AnEditor: TListPropertyEditor);
 procedure UnregisterListPropertyEditor(AnEditor: TListPropertyEditor);
 procedure UpdateListPropertyEditors(AnObject: TObject);
@@ -1825,6 +1866,35 @@
     procedure Gather(Child: TComponent);
   end;
 
+{ TSelectionEditor }
+
+constructor TSelectionEditor.Create(ADesigner: TIDesigner;
+  AHook: TPropertyEditorHook);
+begin
+  inherited Create(ADesigner, AHook);
+  FDesigner := ADesigner;
+  FHook := AHook;
+end;
+
+function TSelectionEditor.GetAttributes: TSelectionEditorAttributes;
+begin
+  Result := [];
+end;
+
+procedure TSelectionEditor.FilterProperties(
+  ASelection: TPersistentSelectionList; AProperties: TPropertyEditorList);
+begin
+
+end;
+
+{ TBaseSelectionEditor }
+
+constructor TBaseSelectionEditor.Create(ADesigner: TIDesigner;
+  AHook: TPropertyEditorHook);
+begin
+
+end;
+
 { TPagesPropertyEditor }
 
 procedure TPagesPropertyEditor.AssignItems(OldItmes, NewItems: TStrings);
@@ -2121,6 +2191,7 @@
 var
   PropertyEditorMapperList:TFPList;
   PropertyClassList:TFPList;
+  SelectionEditorClassList:TFPList;
 
 type
   PPropertyClassRec=^TPropertyClassRec;
@@ -2136,6 +2207,12 @@
     Mapper:TPropertyEditorMapperFunc;
   end;
 
+  PSelectionEditorClassRec=^TSelectionEditorClassRec;
+  TSelectionEditorClassRec=record
+    ComponentClass:TComponentClass;
+    EditorClass:TSelectionEditorClass;
+  end;
+
 { TPropInfoList }
 
 constructor TPropInfoList.Create(Instance:TPersistent; Filter:TTypeKinds);
@@ -2288,7 +2365,70 @@
 
 //------------------------------------------------------------------------------
 
+procedure RegisterSelectionEditor(AComponentClass: TComponentClass; AEditorClass: TSelectionEditorClass);
+var
+  p:PSelectionEditorClassRec;
+begin
+  if not Assigned(AComponentClass) or not Assigned(AEditorClass) then
+    Exit;
+  if not Assigned(SelectionEditorClassList) then
+    SelectionEditorClassList:=TFPList.Create;
+  New(p);
+  p^.ComponentClass:=AComponentClass;
+  p^.EditorClass:=AEditorClass;
+  SelectionEditorClassList.Add(p);
+end;
 
+procedure GetSelectionEditorClasses(AComponent: TComponent; AEditorList: TSelectionEditorClassList);
+var
+  i:LongInt;
+begin
+  if not Assigned(AComponent) or not Assigned(AEditorList) then
+    Exit;
+  if not Assigned(SelectionEditorClassList) then
+    Exit;
+  for i:=0 to SelectionEditorClassList.Count-1 do begin
+    with PSelectionEditorClassRec(SelectionEditorClassList[i])^ do begin
+      if AComponent.InheritsFrom(ComponentClass) then
+        AEditorList.Add(EditorClass);
+    end;
+  end;
+end;
+
+procedure GetSelectionEditorClasses(ASelection: TPersistentSelectionList;
+  AEditorList: TSelectionEditorClassList);
+var
+  tmp:TSelectionEditorClassList;
+  i,j:LongInt;
+  sel:TPersistent;
+begin
+  if not Assigned(ASelection) or (ASelection.Count=0) or not Assigned(AEditorList) then
+    Exit;
+
+  tmp:=TSelectionEditorClassList.Create;
+  try
+    for i:=0 to ASelection.Count-1 do begin
+      sel:=ASelection[i];
+      if not (sel is TComponent) then
+        Continue;
+      GetSelectionEditorClasses(TComponent(sel),tmp);
+      { if there are no classes yet, we pick them as is, otherwise we remove all
+        those from the existing list that are not part of the new list }
+      if AEditorList.Count=0 then
+        AEditorList.Assign(tmp)
+      else begin
+        for j:=AEditorList.Count-1 downto 0 do begin
+          if tmp.IndexOf(AEditorList[j])<0 then
+            AEditorList.Delete(j);
+        end;
+      end;
+      tmp.Clear;
+    end;
+  finally
+    tmp.Free;
+  end;
+end;
+
 { GetComponentProperties }
 
 procedure RegisterPropertyEditor(PropertyType:PTypeInfo;
@@ -2401,10 +2541,14 @@
   Candidates: TPropInfoList;
   PropLists: TFPList;
   PropEditor: TPropertyEditor;
+  PropEditorList: TPropertyEditorList;
+  SelEditor: TBaseSelectionEditor;
+  SelEditorList: TSelectionEditorClassList;
   EdClass: TPropertyEditorClass;
   PropInfo: PPropInfo;
   AddEditor: Boolean;
   Instance: TPersistent;
+  Designer: TIDesigner;
 begin
   if (ASelection = nil) or (ASelection.Count = 0) then Exit;
   SelCount := ASelection.Count;
@@ -2448,52 +2592,82 @@
       PropEditor.Free;
     end;
 
-    PropLists := TFPList.Create;
+    PropEditorList := TPropertyEditorList.Create(True);
     try
-      PropLists.Count := SelCount;
-      // Create a property info list for each component in the selection
-      for I := 0 to SelCount - 1 do
-        PropLists[i] := TPropInfoList.Create(ASelection[I], AFilter);
+      PropLists := TFPList.Create;
+      try
+        PropLists.Count := SelCount;
+        // Create a property info list for each component in the selection
+        for I := 0 to SelCount - 1 do
+          PropLists[i] := TPropInfoList.Create(ASelection[I], AFilter);
 
-      // Eliminate each property in Candidates that is not in all property lists
-      for I := 0 to SelCount - 1 do
-        Candidates.Intersect(TPropInfoList(PropLists[I]));
+        // Eliminate each property in Candidates that is not in all property lists
+        for I := 0 to SelCount - 1 do
+          Candidates.Intersect(TPropInfoList(PropLists[I]));
 
-      // Eliminate each property in the property list that are not in Candidates
-      for I := 0 to SelCount - 1 do
-        TPropInfoList(PropLists[I]).Intersect(Candidates);
+        // Eliminate each property in the property list that are not in Candidates
+        for I := 0 to SelCount - 1 do
+          TPropInfoList(PropLists[I]).Intersect(Candidates);
 
-      // PropList now has a matrix of PropInfo's.
-      // -> create a property editor for each property
-      for I := 0 to Candidates.Count - 1 do
-      begin
-        EdClass := GetEditorClass(Candidates[I], Instance);
-        if EdClass = nil then Continue;
-        PropEditor := EdClass.Create(AHook, SelCount);
-        AddEditor := True;
-        for J := 0 to SelCount - 1 do
+        // PropList now has a matrix of PropInfo's.
+        // -> create a property editor for each property
+        for I := 0 to Candidates.Count - 1 do
         begin
-          if (ASelection[J].ClassType <> ClassTyp) and
-            (GetEditorClass(TPropInfoList(PropLists[J])[I], ASelection[J])<>EdClass) then
+          EdClass := GetEditorClass(Candidates[I], Instance);
+          if EdClass = nil then Continue;
+          PropEditor := EdClass.Create(AHook, SelCount);
+          AddEditor := True;
+          for J := 0 to SelCount - 1 do
           begin
-            AddEditor := False;
-            Break;
+            if (ASelection[J].ClassType <> ClassTyp) and
+              (GetEditorClass(TPropInfoList(PropLists[J])[I], ASelection[J])<>EdClass) then
+            begin
+              AddEditor := False;
+              Break;
+            end;
+            PropEditor.SetPropEntry(J, ASelection[J], TPropInfoList(PropLists[J])[I]);
           end;
-          PropEditor.SetPropEntry(J, ASelection[J], TPropInfoList(PropLists[J])[I]);
+          if AddEditor then
+          begin
+            PropEditor.Initialize;
+            if not PropEditor.ValueAvailable then AddEditor:=false;
+          end;
+          if AddEditor then begin
+            DebugLn('Adding property editor for %s', [PropEditor.GetName]);
+            PropEditorList.Add(PropEditor)
+          end
+          else
+            PropEditor.Free;
         end;
-        if AddEditor then
-        begin
-          PropEditor.Initialize;
-          if not PropEditor.ValueAvailable then AddEditor:=false;
+      finally
+        for I := 0 to PropLists.Count - 1 do TPropInfoList(PropLists[I]).Free;
+        PropLists.Free;
+      end;
+
+      SelEditorList := TSelectionEditorClassList.Create;
+      try
+        GetSelectionEditorClasses(ASelection, SelEditorList);
+        { is it safe to assume that the whole selection has the same designer? }
+        Designer := FindRootDesigner(ASelection[0]);
+        for I := 0 to SelEditorList.Count - 1 do begin
+          SelEditor := SelEditorList[I].Create(Designer, AHook);
+          try
+            if seaFilterProperties in SelEditor.GetAttributes then
+              SelEditor.FilterProperties(ASelection, PropEditorList);
+          finally
+            SelEditor.Free;
+          end;
         end;
-        if AddEditor then
-          AProc(PropEditor)
-        else
-          PropEditor.Free;
+      finally
+        SelEditorList.Free;
       end;
+
+      { no longer free the editors }
+      PropEditorList.FreeObjects := False;
+      for I := 0 to PropEditorList.Count - 1 do
+        AProc(PropEditorList[I]);
     finally
-      for I := 0 to PropLists.Count - 1 do TPropInfoList(PropLists[I]).Free;
-      PropLists.Free;
+      PropEditorList.Free;
     end;
   finally
     Candidates.Free;
@@ -8107,6 +8281,7 @@
   i: integer;
   pm: PPropertyEditorMapperRec;
   pc: PPropertyClassRec;
+  sec: PSelectionEditorClassRec;
 begin
   if PropertyEditorMapperList<>nil then begin
     for i:=0 to PropertyEditorMapperList.Count-1 do begin
@@ -8124,6 +8299,13 @@
     FreeAndNil(PropertyClassList);
   end;
 
+  if Assigned(SelectionEditorClassList) then begin
+    for i:=0 to SelectionEditorClassList.Count-1 do begin
+      sec:=PSelectionEditorClassRec(SelectionEditorClassList[i]);
+      Dispose(sec);
+    end;
+  end;
+
   FreeAndNil(ListPropertyEditors);
   FreeAndNil(VirtualKeyStrings);
 end;
-------------- next part --------------
Index: components/ideintf/fpmake.pp
===================================================================
--- components/ideintf/fpmake.pp	(revision 62212)
+++ components/ideintf/fpmake.pp	(working copy)
@@ -94,6 +94,7 @@
     t.Dependencies.AddUnit('projectresourcesintf');
     t.Dependencies.AddUnit('propedits');
     t.Dependencies.AddUnit('propeditutils');
+    t.Dependencies.AddUnit('seledits');
     t.Dependencies.AddUnit('srceditorintf');
     t.Dependencies.AddUnit('statusbarpropedit');
     t.Dependencies.AddUnit('stringspropeditdlg');
@@ -153,6 +154,7 @@
     T:=P.Targets.AddUnit('projectresourcesintf.pas');
     T:=P.Targets.AddUnit('propedits.pp');
     T:=P.Targets.AddUnit('propeditutils.pp');
+    T:=P.Targets.AddUnit('seledits.pas');
     T:=P.Targets.AddUnit('srceditorintf.pas');
     T:=P.Targets.AddUnit('statusbarpropedit.pp');
     T:=P.Targets.AddUnit('stringspropeditdlg.pas');
Index: components/ideintf/ideintf.lpk
===================================================================
--- components/ideintf/ideintf.lpk	(revision 62212)
+++ components/ideintf/ideintf.lpk	(working copy)
@@ -17,7 +17,7 @@
     <Description Value="IDEIntf - the interface units for the Lazarus IDE"/>
     <License Value="Modified LGPL2"/>
     <Version Major="1"/>
-    <Files Count="86">
+    <Files Count="87">
       <Item1>
         <Filename Value="actionseditor.pas"/>
         <UnitName Value="ActionsEditor"/>
@@ -363,6 +363,10 @@
         <Filename Value="projectgroupintf.pp"/>
         <UnitName Value="ProjectGroupIntf"/>
       </Item86>
+      <Item87>
+        <Filename Value="seledits.pas"/>
+        <UnitName Value="seledits"/>
+      </Item87>
     </Files>
     <LazDoc Paths="docs"/>
     <i18n>
Index: components/ideintf/ideintf.pas
===================================================================
--- components/ideintf/ideintf.pas	(revision 62212)
+++ components/ideintf/ideintf.pas	(working copy)
@@ -23,7 +23,7 @@
   StatusBarPropEdit, StringsPropEditDlg, TextTools, TreeViewPropEdit, 
   UnitResources, ProjPackIntf, DBGridColumnsPropEditForm, ToolBarIntf, 
   ChangeParentDlg, PackageDependencyIntf, PackageLinkIntf, FppkgIntf, 
-  LazMsgDialogs, ProjectGroupIntf, LazarusPackageIntf;
+  LazMsgDialogs, ProjectGroupIntf, SelEdits, LazarusPackageIntf;
 
 implementation
 
Index: components/ideintf/seledits.pas
===================================================================
--- components/ideintf/seledits.pas	(nonexistent)
+++ components/ideintf/seledits.pas	(working copy)
@@ -0,0 +1,149 @@
+{
+ *****************************************************************************
+  See the file COPYING.modifiedLGPL.txt, included in this distribution,
+  for details about the license.
+ *****************************************************************************
+
+  Author: Sven Barth
+
+  Abstract:
+    This unit contains selection editors for various LCL components.
+}
+unit SelEdits;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  // RTL / FCL
+  SysUtils, TypInfo,
+  // LCL
+  Controls, ExtCtrls,
+  // IdeIntf
+  PropEdits;
+
+type
+
+  { TFlowPanelControlIndexEditor }
+
+  TFlowPanelControlIndexEditor = class(TSelectionEditor)
+  public
+    function GetAttributes: TSelectionEditorAttributes; override;
+    procedure FilterProperties(ASelection: TPersistentSelectionList;
+      AProperties: TPropertyEditorList); override;
+  end;
+
+implementation
+
+type
+
+  { TFlowPanelControlIndexProperty }
+
+  TFlowPanelControlIndexProperty = class(TPropertyEditor)
+  private
+    FPanel: TCustomFlowPanel;
+    FControl: TControl;
+    FPropInfo: TPropInfo;
+    FPropType: PTypeInfo;
+  public
+    constructor Create(APanel: TCustomFlowPanel; AControl: TControl; AHook: TPropertyEditorHook); reintroduce;
+    function GetAttributes: TPropertyAttributes; override;
+    function GetValue: ansistring; override;
+    procedure SetValue(const NewValue: ansistring); override;
+  end;
+
+{ TFlowPanelControlIndexProperty }
+
+constructor TFlowPanelControlIndexProperty.Create(APanel: TCustomFlowPanel;
+  AControl: TControl; AHook: TPropertyEditorHook);
+begin
+  inherited Create(AHook, 1);
+  FPanel := APanel;
+  FControl := AControl;
+  FPropType := TypeInfo(Integer);
+{$if FPC_FULLVERSION<30101}
+  FPropInfo.PropType := FPropType;
+{$else}
+  FPropInfo.PropTypeRef := @FPropType;
+{$endif}
+  FPropInfo.Name := 'ControlIndex';
+  SetPropEntry(0, Nil, @FPropInfo);
+end;
+
+function TFlowPanelControlIndexProperty.GetAttributes: TPropertyAttributes;
+begin
+  Result := [];
+end;
+
+function TFlowPanelControlIndexProperty.GetValue: ansistring;
+begin
+  Result := IntToStr(FPanel.GetControlIndex(FControl));
+end;
+
+procedure TFlowPanelControlIndexProperty.SetValue(const NewValue: ansistring);
+var
+  idx: Integer;
+begin
+  if not TryStrToInt(NewValue, idx) then
+    Exit;
+  FPanel.SetControlIndex(FControl, idx);
+end;
+
+{ TFlowPanelControlIndexEditor }
+
+function TFlowPanelControlIndexEditor.GetAttributes: TSelectionEditorAttributes;
+begin
+  Result := [seaFilterProperties];
+end;
+
+procedure TFlowPanelControlIndexEditor.FilterProperties(
+  ASelection: TPersistentSelectionList; AProperties: TPropertyEditorList);
+var
+  ctrl: TControl;
+  i: LongInt;
+  todelete: array[0..1] of LongInt;
+  propname: ShortString;
+begin
+  if not Assigned(ASelection) or not Assigned(AProperties) then
+    Exit;
+  if ASelection.Count <> 1 then
+    Exit;
+  todelete[0] := -1;
+  todelete[1] := -1;
+  for i := 0 to AProperties.Count - 1 do begin
+    if AProperties[i] is TFlowPanelControlIndexProperty then
+      Exit;
+    propname := UpperCase(AProperties[i].GetName);
+    if propname = 'CONTROLINDEX' then
+      Exit
+    else if propname = 'LEFT' then
+      todelete[0] := i
+    else if propname = 'TOP' then
+      todelete[1] := i;
+  end;
+
+  if not (ASelection[0] is TControl) then
+    Exit;
+  ctrl := TControl(ASelection[0]);
+  if not (ctrl.Parent is TCustomFlowPanel) then
+    Exit;
+
+  if todelete[0] < todelete[1] then begin
+    i := todelete[0];
+    todelete[0] := todelete[1];
+    todelete[1] := i;
+  end;
+  for i := Low(todelete) to High(todelete) do
+    if todelete[i] >= 0 then
+      AProperties.Delete(todelete[i]);
+
+  AProperties.Add(TFlowPanelControlIndexProperty.Create(TCustomFlowPanel(ctrl.Parent), ctrl, Hook));
+end;
+
+initialization
+  { we need to register this for TControl as this is applied to each control
+    that is placed on a TFlowPanel }
+  RegisterSelectionEditor(TControl, TFlowPanelControlIndexEditor);
+end.
+

Property changes on: components/ideintf/seledits.pas
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/pascal
\ No newline at end of property
Index: ide/main.pp
===================================================================
--- ide/main.pp	(revision 62212)
+++ ide/main.pp	(working copy)
@@ -82,6 +82,7 @@
   SrcEditorIntf, NewItemIntf, IDEExternToolIntf, IDEMsgIntf, LazMsgDialogs,
   PackageIntf, ProjectIntf, CompOptsIntf, MenuIntf, BaseIDEIntf, LazIDEIntf,
   IDEOptionsIntf, IDEOptEditorIntf, IDEImagesIntf, ComponentEditors, ToolBarIntf,
+  SelEdits,
   // protocol
   IDEProtocol,
   // compile
-------------- next part --------------
Index: components/ideintf/objectinspector.pp
===================================================================
--- components/ideintf/objectinspector.pp	(revision 62212)
+++ components/ideintf/objectinspector.pp	(working copy)
@@ -1589,7 +1589,7 @@
   isExcept := false;
   Editor:=CurRow.Editor;
   prpInfo := nil;
-  if CompEditDsg<>nil then begin
+  if (CompEditDsg<>nil) and (paRevertable in Editor.GetAttributes) then begin
     SetLength(OldUndoValues, Editor.PropCount);
     prpInfo := Editor.GetPropInfo;
     if prpInfo<>nil then begin
@@ -1621,7 +1621,7 @@
     end;
 
     // add Undo action
-    if (not isExcept) and (CompEditDsg<>nil) then
+    if (not isExcept) and (CompEditDsg<>nil) and (paRevertable in Editor.GetAttributes) then
     begin
       for i := 0 to Editor.PropCount - 1 do
       begin


More information about the lazarus mailing list