lazarus/designer/changeclassdialog.pas
2006-07-22 13:54:55 +00:00

490 lines
15 KiB
ObjectPascal

{ /***************************************************************************
ChangeClassDialog.pas - Lazarus IDE unit
----------------------------------------
***************************************************************************/
***************************************************************************
* *
* This source is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This code is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* A copy of the GNU General Public License is available on the World *
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
Functions and Dialog to change the class of a designer component.
}
unit ChangeClassDialog;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, LCLProc, LResources, Forms, Controls, Graphics, Dialogs,
StdCtrls, Buttons, AVGLvlTree, LFMTrees, CodeCache, CodeToolManager,
// IDE
SrcEditorIntf, PropEdits, LazarusIDEStrConsts, ComponentReg, ComponentEditors,
FormEditingIntf, CheckLFMDlg, Project, MainIntf;
type
TChangeClassDlg = class(TForm)
NewClassComboBox: TComboBox;
NewAncestorGroupBox: TGroupBox;
NewAncestorsListBox: TListBox;
OldAncestorGroupBox: TGroupBox;
OldAncestorsListBox: TListBox;
OldClassLabel: TLabel;
OkButton: TButton;
CancelButton: TButton;
NewGroupBox: TGroupBox;
OldGroupBox: TGroupBox;
procedure ChangeClassDlgCreate(Sender: TObject);
procedure NewClassComboBoxEditingDone(Sender: TObject);
private
FClasses: TAvgLvlTree;
FNewClass: TClass;
FThePersistent: TPersistent;
procedure SetNewClass(const AValue: TClass);
procedure SetThePersistent(const AValue: TPersistent);
procedure UpdateInfo;
procedure UpdateOldInfo;
procedure UpdateNewInfo;
procedure FillAncestorListBox(AClass: TClass; AListBox: TListBox);
procedure AddClass(const AClass: TPersistentClass);
procedure AddComponentClass(const AClass: TComponentClass);
function CompareClasses(Tree: TAvgLvlTree; Class1, Class2: TClass): integer;
public
destructor Destroy; override;
procedure FillNewClassComboBox;
property ThePersistent: TPersistent read FThePersistent write SetThePersistent;
property NewClass: TClass read FNewClass write SetNewClass;
end;
function ShowChangeClassDialog(ADesigner: TIDesigner;
APersistent: TPersistent): TModalResult;
function ChangePersistentClass(ADesigner: TIDesigner;
APersistent: TPersistent; NewClass: TClass): TModalResult;
implementation
function ShowChangeClassDialog(ADesigner: TIDesigner;
APersistent: TPersistent): TModalResult;
var
ChangeClassDlg: TChangeClassDlg;
begin
Result:=mrCancel;
//MessageDlg('Not implemented yet','Not implemented yet',mtInformation,[mbOk],0);
//exit;
ChangeClassDlg:=TChangeClassDlg.Create(nil);
try
ChangeClassDlg.ThePersistent:=APersistent;
ChangeClassDlg.FillNewClassComboBox;
if ChangeClassDlg.ShowModal=mrOk then begin
Result:=ChangePersistentClass(ADesigner,APersistent,
ChangeClassDlg.NewClass);
end;
finally
ChangeClassDlg.Free;
end;
end;
function ChangePersistentClass(ADesigner: TIDesigner;
APersistent: TPersistent; NewClass: TClass): TModalResult;
var
ComponentStream: TMemoryStream;
PersistentName: String;
UnitCode: TCodeBuffer;
LFMBuffer: TCodeBuffer;
LFMTree: TLFMTree;
UnitInfo: TUnitInfo;
OldParents: TStrings; // Name=OldParent pairs
procedure ShowAbortMessage(const Msg: string);
begin
MessageDlg('Error',
Format(lisUnableToChangeClassOfTo, [Msg, #13, PersistentName,
NewClass.ClassName]),
mtError,[mbCancel],0);
end;
function StreamSelection: boolean;
begin
Result:=false;
// select only this persistent
GlobalDesignHook.SelectOnlyThis(APersistent);
if (APersistent is TControl)
and (TControl(APersistent).Parent<>nil) then begin
if OldParents=nil then
OldParents:=TStringList.Create;
OldParents.Values[TControl(APersistent).Name]:=
TControl(APersistent).Parent.Name;
end;
// stream selection
ComponentStream:=TMemoryStream.Create;
if (not FormEditingHook.SaveSelectionToStream(ComponentStream))
or (ComponentStream.Size=0) then begin
ShowAbortMessage(lisUnableToStreamSelectedComponents2);
exit;
end;
Result:=true;
end;
function ParseLFMStream: boolean;
var
SrcEdit: TSourceEditorInterface;
Msg: String;
begin
Result:=false;
if not CodeToolBoss.GatherExternalChanges then begin
ShowAbortMessage(lisUnableToGatherEditorChanges);
exit;
end;
MainIDEInterface.GetUnitInfoForDesigner(ADesigner,SrcEdit,UnitInfo);
if UnitInfo=nil then begin
ShowAbortMessage(lisUnableToGetSourceForDesigner);
exit;
end;
UnitCode:=UnitInfo.Source;
LFMBuffer:=CodeToolBoss.CreateTempFile('changeclass.lfm');
if (LFMBuffer=nil) or (ComponentStream.Size=0) then begin
ShowAbortMessage(lisUnableToCreateTemporaryLfmBuffer);
exit;
end;
ComponentStream.Position:=0;
LFMBuffer.LoadFromStream(ComponentStream);
if not CodeToolBoss.CheckLFM(UnitCode,LFMBuffer,LFMTree,false,false) then
begin
debugln('ChangePersistentClass-Before--------------------------------------------');
debugln(LFMBuffer.Source);
debugln('ChangePersistentClass-Before--------------------------------------------');
if CodeToolBoss.ErrorMessage<>'' then
MainIDEInterface.DoJumpToCodeToolBossError
else begin
Msg:=lisErrorParsingLfmComponentStream;
if LFMTree<>nil then Msg:=Msg+#13#13+LFMTree.FirstErrorAsString+#13;
ShowAbortMessage(Msg);
end;
exit;
end;
Result:=true;
end;
function ChangeClassName: boolean;
var
CurNode: TLFMTreeNode;
ObjectNode: TLFMObjectNode;
begin
Result:=false;
// find classname position
CurNode:=LFMTree.Root;
while CurNode<>nil do begin
if (CurNode is TLFMObjectNode) then begin
ObjectNode:=TLFMObjectNode(CurNode);
if (CompareText(ObjectNode.Name,(APersistent as TComponent).Name)=0)
and (CompareText(ObjectNode.TypeName,APersistent.ClassName)=0) then begin
// replace classname
LFMBuffer.Replace(ObjectNode.TypeNamePosition,length(ObjectNode.TypeName),
NewClass.ClassName);
Result:=true;
exit;
end;
end;
CurNode:=CurNode.NextSibling;
end;
ShowAbortMessage(Format(lisUnableToFindInLFMStream, [PersistentName]));
end;
function CheckProperties: boolean;
begin
Result:=CheckLFMBuffer(UnitCode,LFMBuffer,nil,false,false)=mrOk;
if not Result and (CodeToolBoss.ErrorMessage<>'') then
MainIDEInterface.DoJumpToCodeToolBossError;
end;
function InsertStreamedSelection: boolean;
var
MemStream: TMemoryStream;
LFMType, LFMComponentName, LFMClassName: string;
AComponent: TComponent;
NewParent: TWinControl;
NewParentName: string;
begin
Result:=false;
if LFMBuffer.SourceLength=0 then exit;
MemStream:=TMemoryStream.Create;
try
debugln('ChangePersistentClass-After--------------------------------------------');
debugln(LFMBuffer.Source);
debugln('ChangePersistentClass-After--------------------------------------------');
LFMBuffer.SaveToStream(MemStream);
MemStream.Position:=0;
NewParent:=nil;
if OldParents<>nil then begin
ReadLFMHeader(MemStream,LFMType,LFMComponentName,LFMClassName);
if (LFMType='') or (LFMClassName='') then ;
MemStream.Position:=0;
if LFMComponentName<>'' then begin
NewParentName:=OldParents.Values[LFMComponentName];
if NewParentName<>'' then begin
AComponent:=GlobalDesignHook.GetComponent(NewParentName);
if AComponent is TWinControl then
NewParent:=TWinControl(AComponent);
end;
end;
end;
Result:=FormEditingHook.InsertFromStream(MemStream,NewParent,
[cpsfReplace]);
if not Result then
ShowAbortMessage(lisReplacingSelectionFailed);
finally
MemStream.Free;
end;
end;
begin
Result:=mrCancel;
if CompareText(APersistent.ClassName,NewClass.ClassName)=0 then begin
Result:=mrOk;
exit;
end;
PersistentName:=APersistent.ClassName;
if APersistent is TComponent then begin
PersistentName:=TComponent(APersistent).Name+':'+PersistentName;
end else begin
ShowAbortMessage(lisCanOnlyChangeTheClassOfTComponents);
exit;
end;
ComponentStream:=nil;
LFMTree:=nil;
OldParents:=nil;
try
if not StreamSelection then exit;
if not ParseLFMStream then exit;
if not ChangeClassName then exit;
if not CheckProperties then exit;
if not InsertStreamedSelection then exit;
finally
ComponentStream.Free;
LFMTree.Free;
OldParents.Free;
end;
Result:=mrOk;
end;
{ TChangeClassDlg }
procedure TChangeClassDlg.ChangeClassDlgCreate(Sender: TObject);
begin
OldGroupBox.Caption:=lisOldClass;
NewGroupBox.Caption:=lisNewClass;
OldAncestorGroupBox.Caption:=lisOldAncestors;
NewAncestorGroupBox.Caption:=lisNewAncestors;
OkButton.Caption:=lisLazBuildOk;
CancelButton.Caption:=dlgCancel;
end;
procedure TChangeClassDlg.NewClassComboBoxEditingDone(Sender: TObject);
begin
UpdateNewInfo;
end;
procedure TChangeClassDlg.SetThePersistent(const AValue: TPersistent);
begin
if FThePersistent=AValue then exit;
FThePersistent:=AValue;
UpdateInfo;
end;
procedure TChangeClassDlg.SetNewClass(const AValue: TClass);
begin
if FNewClass=AValue then exit;
FNewClass:=AValue;
UpdateNewInfo;
end;
procedure TChangeClassDlg.UpdateInfo;
begin
UpdateNewInfo;
UpdateOldInfo;
end;
procedure TChangeClassDlg.UpdateOldInfo;
begin
FillAncestorListBox(ThePersistent.ClassType,OldAncestorsListBox);
if ThePersistent<>nil then begin
if ThePersistent is TComponent then
OldClassLabel.Caption:=
TComponent(ThePersistent).Name+':'+ThePersistent.ClassName
else
OldClassLabel.Caption:=ThePersistent.ClassName;
end else begin
OldClassLabel.Caption:='no class';
end;
end;
procedure TChangeClassDlg.UpdateNewInfo;
var
ANode: TAvgLvlTreeNode;
begin
FNewClass:=nil;
if FClasses<>nil then begin
ANode:=FClasses.FindLowest;
while (ANode<>nil) do begin
FNewClass:=TClass(ANode.Data);
if (CompareText(NewClass.ClassName,NewClassComboBox.Text)=0) then
break
else
FNewClass:=nil;
ANode:=FClasses.FindSuccessor(ANode);
end;
end;
FillAncestorListBox(NewClass,NewAncestorsListBox);
if NewClass<>nil then
NewClassComboBox.Text:=NewClass.ClassName
else
NewClassComboBox.Text:='';
end;
procedure TChangeClassDlg.FillAncestorListBox(AClass: TClass; AListBox: TListBox
);
var
List: TStringList;
procedure AddAncestor(CurClass: TClass);
begin
if CurClass=nil then exit;
List.Insert(0,CurClass.ClassName);
AddAncestor(CurClass.ClassParent);
end;
begin
List:=TStringList.Create;
AddAncestor(AClass);
AListBox.Items.Assign(List);
List.Free;
end;
procedure TChangeClassDlg.AddClass(const AClass: TPersistentClass);
begin
if FClasses.FindPointer(AClass)<>nil then exit;
FClasses.Add(AClass);
end;
procedure TChangeClassDlg.AddComponentClass(const AClass: TComponentClass);
begin
AddClass(AClass);
end;
function TChangeClassDlg.CompareClasses(Tree: TAvgLvlTree; Class1,
Class2: TClass): integer;
// sort:
// transforming ThePersistent to descending classes is easy
// transforming ThePersistent to ascending classes is medium
//
// count distance between, that means: find nearest shared ancestor, then
// give two points for every step from ThePersistent to ancestor and one point
// for every step from ancestor to class
//
// otherwise sort for classnames
function AncestorDistance(ChildClass, AncestorClass: TClass): integer;
begin
Result:=0;
while (ChildClass<>nil) and (ChildClass<>AncestorClass) do begin
ChildClass:=ChildClass.ClassParent;
inc(Result);
end;
end;
function RelationDistance(SrcClass, DestClass: TClass): integer;
var
Ancestor: TClass;
begin
// find shared ancestor of
Ancestor:=SrcClass;
while (Ancestor<>nil) and (not DestClass.InheritsFrom(Ancestor)) do
Ancestor:=Ancestor.ClassParent;
// going to the ancestor is normally more difficult than going away
Result:=2*AncestorDistance(SrcClass,Ancestor)
+AncestorDistance(DestClass,Ancestor);
end;
var
Dist1: LongInt;
Dist2: LongInt;
begin
Result:=0;
if (ThePersistent<>nil) then begin
Dist1:=RelationDistance(ThePersistent.ClassType,Class1);
Dist2:=RelationDistance(ThePersistent.ClassType,Class2);
Result:=Dist1-Dist2;
if Result<>0 then exit;
end;
Result:=CompareText(Class1.ClassName,Class2.ClassName);
end;
destructor TChangeClassDlg.Destroy;
begin
FClasses.Free;
FClasses:=nil;
inherited Destroy;
end;
procedure TChangeClassDlg.FillNewClassComboBox;
var
ANode: TAvgLvlTreeNode;
List: TStringList;
begin
// create/clear tree
if FClasses=nil then
FClasses:=TAvgLvlTree.CreateObjectCompare(TObjectSortCompare(@CompareClasses))
else
FClasses.Clear;
// add class of ThePersistent
if ThePersistent<>nil then
AddClass(TPersistentClass(ThePersistent.ClassType));
// add all registered component classes
if (IDEComponentPalette<>nil) then
IDEComponentPalette.IterateRegisteredClasses(@AddComponentClass);
// add list of classnames
List:=TStringList.Create;
ANode:=FClasses.FindLowest;
while ANode<>nil do begin
List.Add(TClass(ANode.Data).ClassName);
ANode:=FClasses.FindSuccessor(ANode);
end;
// assign to combobox
NewClassComboBox.Items.Assign(List);
if (NewClassComboBox.Items.IndexOf(NewClassComboBox.Text)<0)
and (NewClassComboBox.Items.Count>0) then
NewClassComboBox.Text:=NewClassComboBox.Items[0];
UpdateNewInfo;
// clean up
List.Free;
end;
initialization
{$I changeclassdialog.lrs}
end.