lazarus/ide/abstractsmethodsdlg.pas

404 lines
13 KiB
ObjectPascal

{
***************************************************************************
* *
* 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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
A dialog showing the abstract methods of the current class
(at cursor in source editor).
With the ability to implement them automatically by adding empty method
stubs.
}
unit AbstractsMethodsDlg;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils,
Forms, Controls, Graphics, Dialogs, CheckLst, StdCtrls, ExtCtrls, Buttons,
LazLoggerBase,
CodeTree, PascalParserTool, CodeCache, CodeToolManager,
LazIDEIntf, SrcEditorIntf, IDEDialogs,
// IdeUtils
IdeUtilsPkgStrConsts,
// IDE
IdeIntfStrConsts, LazarusIDEStrConsts;
type
{ TAbstractMethodDlgItem }
TAbstractMethodDlgItem = class
public
CodeXYPos: TCodeXYPosition;
ProcHead: string;
BelongsToStartClass: boolean;
end;
{ TAbstractMethodsDialog }
TAbstractMethodsDialog = class(TForm)
AddAllBitBtn: TBitBtn;
NoteLabel: TLabel;
SelectNoneButton: TButton;
SelectAllButton: TButton;
CancelBitBtn: TBitBtn;
AddFirstBitBtn: TBitBtn;
MethodsCheckListBox: TCheckListBox;
MethodsGroupBox: TGroupBox;
BtnPanel: TPanel;
procedure AddAllBitBtnClick(Sender: TObject);
procedure AddFirstBitBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure MethodsCheckListBoxClickCheck(Sender: TObject);
procedure SelectAllButtonClick(Sender: TObject);
procedure SelectNoneButtonClick(Sender: TObject);
private
CodePos: TCodeXYPosition;
TopLine: integer;
FItems: TFPList;// list of TAbstractMethodDlgItem
FCheckingSelection: boolean;
procedure ClearItems;
procedure UpdateButtons;
function CheckSelection: boolean;
function AddOverrides(OnlyFirst: boolean): boolean;
public
NewCode: TCodeBuffer;
NewX,NewY,NewTopLine: integer;
procedure Init(aListOfPCodeXYPosition: TFPList; aCode: TCodeBuffer;
const aCaret: TPoint; aTopLine: integer);
end;
function ShowAbstractMethodsDialog: TModalResult;
implementation
{$R *.lfm}
function ShowAbstractMethodsDialog: TModalResult;
var
AbstractMethodsDialog: TAbstractMethodsDialog;
SrcEdit: TSourceEditorInterface;
Code: TCodeBuffer;
Caret: TPoint;
ErrMsg: String;
ListOfPCodeXYPosition: TFPList;
begin
Result:=mrCancel;
ListOfPCodeXYPosition:=nil;
try
// init codetools
ErrMsg:=lisSAMIDEIsBusy;
if not LazarusIDE.BeginCodeTools then exit;
// get cursor position
ErrMsg:=lisSAMCursorIsNotInAClassDeclaration;
SrcEdit:=SourceEditorManagerIntf.ActiveEditor;
if SrcEdit=nil then exit;
Code:=TCodeBuffer(SrcEdit.CodeToolsBuffer);
if Code=nil then exit;
Caret:=SrcEdit.CursorTextXY;
// check cursor is in a class
if not CodeToolBoss.FindAbstractMethods(Code,Caret.X,Caret.Y,
ListOfPCodeXYPosition,true) then
begin
DebugLn(['ShowAbstractMethodsDialog CodeToolBoss.FindAbstractMethods failed']);
if CodeToolBoss.ErrorMessage<>'' then begin
ErrMsg:='';
LazarusIDE.DoJumpToCodeToolBossError;
end;
exit;
end;
// check if there are abstract methods left to override
if (ListOfPCodeXYPosition=nil) or (ListOfPCodeXYPosition.Count=0) then begin
ErrMsg:='';
IDEMessageDialog(lisSAMNoAbstractMethodsFound,
lisSAMThereAreNoAbstractMethodsLeftToOverride
,mtConfirmation,[mbOk]);
Result:=mrOk;
exit;
end;
ErrMsg:='';
AbstractMethodsDialog:=TAbstractMethodsDialog.Create(nil);
AbstractMethodsDialog.Init(ListOfPCodeXYPosition,Code,Caret,SrcEdit.TopLine);
Result:=AbstractMethodsDialog.ShowModal;
AbstractMethodsDialog.Free;
finally
CodeToolBoss.FreeListOfPCodeXYPosition(ListOfPCodeXYPosition);
if ErrMsg<>'' then begin
IDEMessageDialog(lisCCOErrorCaption,
lisSAMUnableToShowAbstractMethodsOfTheCurrentClassBecaus+LineEnding
+ErrMsg,mtError,[mbCancel]);
end;
end;
end;
{ TAbstractMethodsDialog }
procedure TAbstractMethodsDialog.FormCreate(Sender: TObject);
begin
FItems:=TFPList.Create;
AddFirstBitBtn.Caption:=lisSAMOverrideFirstSelected;
AddAllBitBtn.Caption:=lisSAMOverrideAllSelected;
CancelBitBtn.Caption:=lisCancel;
SelectNoneButton.Caption:=lisSAMSelectNone;
SelectAllButton.Caption:=lisMenuSelectAll;
MethodsGroupBox.Caption:=lisSAMAbstractMethodsNotYetOverridden;
end;
procedure TAbstractMethodsDialog.AddFirstBitBtnClick(Sender: TObject);
begin
if not AddOverrides(true) then exit;
ModalResult:=mrOk;
end;
procedure TAbstractMethodsDialog.AddAllBitBtnClick(Sender: TObject);
begin
if not AddOverrides(false) then exit;
ModalResult:=mrOk;
end;
procedure TAbstractMethodsDialog.FormDestroy(Sender: TObject);
begin
ClearItems;
end;
procedure TAbstractMethodsDialog.MethodsCheckListBoxClickCheck(Sender: TObject);
begin
CheckSelection;
UpdateButtons;
end;
procedure TAbstractMethodsDialog.SelectAllButtonClick(Sender: TObject);
var
i: Integer;
begin
for i:=0 to FItems.Count-1 do
MethodsCheckListBox.Checked[i]:=
not TAbstractMethodDlgItem(FItems[i]).BelongsToStartClass;
end;
procedure TAbstractMethodsDialog.SelectNoneButtonClick(Sender: TObject);
var
i: Integer;
begin
for i:=0 to FItems.Count-1 do
MethodsCheckListBox.Checked[i]:=false;
end;
procedure TAbstractMethodsDialog.ClearItems;
var
i: Integer;
begin
if FItems=nil then exit;
for i:=0 to FItems.Count-1 do
TObject(FItems[i]).Free;
FreeAndNil(FItems);
end;
procedure TAbstractMethodsDialog.UpdateButtons;
var
i: Integer;
begin
i:=MethodsCheckListBox.Items.Count-1;
while (i>=0) and (not MethodsCheckListBox.Checked[i]) do dec(i);
AddFirstBitBtn.Enabled:=i>=0;
AddAllBitBtn.Enabled:=AddFirstBitBtn.Enabled;
end;
function TAbstractMethodsDialog.CheckSelection: boolean;
var
i: Integer;
Item: TAbstractMethodDlgItem;
begin
Result:=true;
if FCheckingSelection then exit;
FCheckingSelection:=true;
try
for i:=0 to FItems.Count-1 do begin
Item:=TAbstractMethodDlgItem(FItems[i]);
if MethodsCheckListBox.Checked[i] and Item.BelongsToStartClass then begin
if Result then begin
IDEMessageDialog(lisCCOErrorCaption,
lisSAMThisMethodCanNotBeOverriddenBecauseItIsDefinedInTh,
mtError,[mbCancel]);
Result:=false;
end;
MethodsCheckListBox.Checked[i]:=false;
end;
end;
finally
FCheckingSelection:=false;
end;
end;
function TAbstractMethodsDialog.AddOverrides(OnlyFirst: boolean): boolean;
var
i, BlockTopLine, BlockBottomLine: Integer;
NewList: TFPList;
Item: TAbstractMethodDlgItem;
begin
Result:=false;
if not CheckSelection then exit;
NewList:=nil;
try
for i:=0 to FItems.Count-1 do begin
if not MethodsCheckListBox.Checked[i] then continue;
Item:=TAbstractMethodDlgItem(FItems[i]);
AddCodePosition(NewList,Item.CodeXYPos);
DebugLn(['TAbstractMethodsDialog.AddOverrides ',Item.CodeXYPos.Code.Filename,' ',Item.CodeXYPos.X,',',Item.CodeXYPos.Y]);
if OnlyFirst then break;
end;
//DebugLn(['TAbstractMethodsDialog.AddOverrides ',CodePos.Code.Filename,' ',CodePos.X,',',CodePos.Y]);
if not CodeToolBoss.AddMethods(CodePos.Code,CodePos.X,CodePos.Y,TopLine,
NewList,true,NewCode,NewX,NewY,NewTopLine,BlockTopLine,BlockBottomLine)
then begin
LazarusIDE.DoJumpToCodeToolBossError;
exit;
end;
LazarusIDE.DoOpenFileAndJumpToPos(NewCode.Filename,Point(NewX,NewY),
NewTopLine,BlockTopLine,BlockBottomLine,-1,-1,[]);
finally
CodeToolBoss.FreeListOfPCodeXYPosition(NewList);
end;
Result:=true;
end;
procedure TAbstractMethodsDialog.Init(aListOfPCodeXYPosition: TFPList;
aCode: TCodeBuffer; const aCaret: TPoint; aTopLine: integer);
var
i: Integer;
CodeXYPos: TCodeXYPosition;
CurTool: TCodeTool;
ListOfPCodeXYPosition: TFPList;
Tool: TCodeTool;
CleanPos: integer;
ClassNode: TCodeTreeNode;
CurNode: TCodeTreeNode;
ProcNode: TCodeTreeNode;
NewItem: TAbstractMethodDlgItem;
StartClassName: String;
BelongsToStartClassCnt: Integer;
NoteStr: String;
begin
ListOfPCodeXYPosition:=aListOfPCodeXYPosition;
if ListOfPCodeXYPosition=nil then begin
DebugLn(['TAbstractMethodsDialog.Init ListOfPCodeXYPosition=nil']);
exit;
end;
CodePos.Code:=aCode;
CodePos.X:=aCaret.X;
CodePos.Y:=aCaret.Y;
TopLine:=aTopLine;
// get Tool and ClassNode
Tool:=CodeToolBoss.GetCodeToolForSource(CodePos.Code,true,false) as TCodeTool;
if Tool.CaretToCleanPos(CodePos,CleanPos)<>0 then begin
DebugLn(['TAbstractMethodsDialog.Init invalid ',CodePos.Code.Filename,' ',CodePos.X,',',CodePos.Y]);
exit;
end;
ClassNode:=Tool.FindDeepestNodeAtPos(CleanPos,false);
if ClassNode=nil then begin
DebugLn(['TAbstractMethodsDialog.Init no node at cursor ',CodePos.Code.Filename,' ',CodePos.X,',',CodePos.Y]);
exit;
end;
if ClassNode.Desc=ctnTypeDefinition then
ClassNode:=ClassNode.FirstChild
else if ClassNode.Desc=ctnGenericType then
ClassNode:=ClassNode.LastChild
else
ClassNode:=Tool.FindClassOrInterfaceNode(ClassNode);
if (ClassNode=nil) then begin
DebugLn(['TAbstractMethodsDialog.Init no class node at cursor ',CodePos.Code.Filename,' ',CodePos.X,',',CodePos.Y]);
exit;
end;
StartClassName:=Tool.ExtractClassName(ClassNode,false);
BelongsToStartClassCnt:=0;
// create items
for i:=0 to ListOfPCodeXYPosition.Count-1 do begin
CodeXYPos:=PCodeXYPosition(ListOfPCodeXYPosition[i])^;
CurTool:=CodeToolBoss.GetCodeToolForSource(CodeXYPos.Code,true,false) as TCodeTool;
if CurTool.CaretToCleanPos(CodeXYPos,CleanPos)<>0 then begin
DebugLn(['TAbstractMethodsDialog.Init skipping ',CodeXYPos.Code.Filename,' ',CodeXYPos.X,',',CodeXYPos.Y]);
continue;
end;
CurNode:=CurTool.FindDeepestNodeAtPos(CleanPos,false);
if CurNode=nil then begin
DebugLn(['TAbstractMethodsDialog.Init no node at ',CodeXYPos.Code.Filename,' ',CodeXYPos.X,',',CodeXYPos.Y]);
continue;
end;
if CurNode.Desc<>ctnProcedure then begin
DebugLn(['TAbstractMethodsDialog.Init no proc node at ',CodeXYPos.Code.Filename,' ',CodeXYPos.X,',',CodeXYPos.Y]);
continue;
end;
ProcNode:=CurNode;
NewItem:=TAbstractMethodDlgItem.Create;
NewItem.CodeXYPos:=CodeXYPos;
NewItem.ProcHead:=CurTool.ExtractProcHead(ProcNode,[phpAddClassname,
phpWithStart,phpWithParameterNames,phpWithVarModifiers,
phpWithDefaultValues,phpWithResultType,
phpWithOfObject,phpWithCallingSpecs]);
NewItem.BelongsToStartClass:=ProcNode.HasAsParent(ClassNode);
if NewItem.BelongsToStartClass then
inc(BelongsToStartClassCnt);
FItems.Add(NewItem);
end;
MethodsCheckListBox.Clear;
for i:=0 to FItems.Count-1 do begin
NewItem:=TAbstractMethodDlgItem(FItems[i]);
MethodsCheckListBox.Items.Add(NewItem.ProcHead);
MethodsCheckListBox.Checked[i]:=not NewItem.BelongsToStartClass;
end;
// caption
Caption:=Format(lisSAMAbstractMethodsOf, [StartClassName]);
// note
NoteStr:='';
if BelongsToStartClassCnt>0 then begin
NoteStr:=Format(lisSAMIsAnAbstractClassItHasAbstractMethods,
[StartClassName, IntToStr(BelongsToStartClassCnt)])+LineEnding;
end;
NoteStr:=NoteStr+
Format(lisSAMThereAreAbstractMethodsToOverrideSelectTheMethodsF,
[IntToStr(FItems.Count-BelongsToStartClassCnt), LineEnding]);
NoteLabel.Caption:=NoteStr;
UpdateButtons;
end;
end.