{*************************************************************************** * * * 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 . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** Abstract: Frame to edit build macros and conditionals of compiler options (project+packages). } unit Compiler_BuildMacro_Options; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Math, types, AVL_Tree, LCLProc, FileUtil, Controls, Forms, StdCtrls, Grids, LCLType, Buttons, ExtCtrls, Dialogs, ComCtrls, Menus, AvgLvlTree, SynEdit, SynHighlighterPas, SynEditKeyCmds, SynCompletion, KeywordFuncLists, CodeToolsCfgScript, IDEImagesIntf, IDECommands, ProjectIntf, PackageIntf, IDEOptionsIntf, MacroIntf, CompilerOptions, EditorOptions, CompOptsIntf, IDEDialogs, LazarusIDEStrConsts, CompOptsModes, SourceSynEditor, PackageDefs; type TCBMNodeType = ( cbmntNone, cbmntBuildMacro, cbmntValue ); TCBMPrefixType = ( cbmpShort, cbmpMedium, cbmpLong ); { TCompOptBuildMacrosFrame } TCompOptBuildMacrosFrame = class(TAbstractIDEOptionsEditor) BMAddMacroSpeedButton: TSpeedButton; BMAddMacroValueSpeedButton: TSpeedButton; BMDeleteMacroSpeedButton: TSpeedButton; BuildMacroDescriptionEdit: TEdit; BuildMacroSelectedGroupBox: TGroupBox; BuildMacrosTreeView: TTreeView; BuildMacroDefaultLabel: TLabel; BuildMacroDescriptionLabel: TLabel; ConditionalsGroupBox: TGroupBox; CondStatusbar: TStatusBar; CondSynEdit: TSynEdit; MacrosGroupBox: TGroupBox; MacrosSplitter: TSplitter; Splitter1: TSplitter; procedure BMAddMacroSpeedButtonClick(Sender: TObject); procedure BMAddMacroValueSpeedButtonClick(Sender: TObject); procedure BMDeleteMacroSpeedButtonClick(Sender: TObject); procedure BuildMacroDescriptionEditExit(Sender: TObject); procedure BuildMacrosTreeViewEdited(Sender: TObject; Node: TTreeNode; var S: string); procedure BuildMacrosTreeViewEditing(Sender: TObject; Node: TTreeNode; var AllowEdit: Boolean); procedure BuildMacrosTreeViewSelectionChanged(Sender: TObject); procedure CondSynEditChange(Sender: TObject); procedure CondSynEditKeyPress(Sender: TObject; var Key: char); procedure CondSynEditProcessUserCommand(Sender: TObject; var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer); procedure CondSynEditStatusChange(Sender: TObject; Changes: TSynStatusChanges); procedure fSynCompletionCancel(Sender: TObject); procedure fSynCompletionExecute(Sender: TObject); procedure fSynCompletionKeyCompletePrefix(Sender: TObject); procedure fSynCompletionKeyDelete(Sender: TObject); procedure fSynCompletionKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure fSynCompletionKeyNextChar(Sender: TObject); procedure fSynCompletionKeyPrevChar(Sender: TObject); procedure fSynCompletionSearchPosition(var Position: integer); procedure fSynCompletionUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char); procedure fSynCompletionValidate(Sender: TObject; KeyChar: TUTF8Char; Shift: TShiftState); procedure OnIdle(Sender: TObject; var Done: Boolean); private FCompletionHistory: TStrings; FCompletionValues: TStrings; FDefaultVariables: TCTCfgScriptVariables; FHighlighter: TIDESynFreePasSyn; FBuildMacros: TIDEBuildMacros; FIdleConnected: Boolean; FIsPackage: boolean; FMacrosOwner: TObject; FStatusMessage: string; fVarImgID: LongInt; fValueImgID: LongInt; fDefValueImgID: LongInt; fEngine: TIDECfgScriptEngine; fSynCompletion: TSynCompletion; procedure SetBuildMacros(const AValue: TIDEBuildMacros); procedure RebuildTreeView; procedure SetIdleConnected(const AValue: Boolean); procedure SetStatusMessage(const AValue: string); function TreeViewAddBuildMacro(aBuildMacro: TLazBuildMacro): TTreeNode; procedure TreeViewAddValue(ValuesTVNode: TTreeNode; aValue: string); function GetNodeInfo(Node: TTreeNode; out BuildMacro: TLazBuildMacro): TCBMNodeType; function GetSelectedNode(out aBuildMacro: TLazBuildMacro; out NodeType: TCBMNodeType): TTreeNode; function GetBuildMacroTVNode(aBuildMacro: TLazBuildMacro): TTreeNode; function GetMacroNamePrefix(PrefixType: TCBMPrefixType): string; procedure UpdateItemPropertyControls; procedure UpdateMessages; procedure UpdateStatusBar; procedure StartCompletion; procedure UpdateCompletionValues; function GetCondCursorWord: string; public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; function GetTitle: String; override; procedure Setup(ADialog: TAbstractOptionsEditorDialog); override; class function SupportedOptionsClass: TAbstractIDEOptionsClass; override; procedure ReadSettings(AOptions: TAbstractIDEOptions); override; procedure WriteSettings(AOptions: TAbstractIDEOptions); override; property BuildMacros: TIDEBuildMacros read FBuildMacros write SetBuildMacros; // local copy property MacrosOwner: TObject read FMacrosOwner; procedure LoadFromOptions(Options: TBaseCompilerOptions); procedure SaveToOptions(Options: TBaseCompilerOptions); property IdleConnected: Boolean read FIdleConnected write SetIdleConnected; property StatusMessage: string read FStatusMessage write SetStatusMessage; property DefaultVariables: TCTCfgScriptVariables read FDefaultVariables; property IsPackage: boolean read FIsPackage; property CompletionValues: TStrings read FCompletionValues; property CompletionHistory: TStrings read FCompletionHistory; end; implementation {$R *.lfm} { TCompOptBuildMacrosFrame } procedure TCompOptBuildMacrosFrame.OnIdle(Sender: TObject; var Done: Boolean); begin IdleConnected:=false; UpdateMessages; end; procedure TCompOptBuildMacrosFrame.CondSynEditChange(Sender: TObject); begin UpdateStatusBar; IdleConnected:=true; end; procedure TCompOptBuildMacrosFrame.CondSynEditKeyPress(Sender: TObject; var Key: char); begin //debugln(['TCompOptBuildMacrosFrame.CondSynEditKeyPress ',ord(Key)]); end; procedure TCompOptBuildMacrosFrame.CondSynEditProcessUserCommand( Sender: TObject; var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer); begin if (Command=ecWordCompletion) or (Command=ecIdentCompletion) then StartCompletion; end; procedure TCompOptBuildMacrosFrame.CondSynEditStatusChange(Sender: TObject; Changes: TSynStatusChanges); begin if fSynCompletion.TheForm.Visible then begin //debugln(['TCompOptBuildMacrosFrame.CondSynEditStatusChange ']); fSynCompletion.CurrentString := GetCondCursorWord; end; UpdateStatusBar; end; procedure TCompOptBuildMacrosFrame.fSynCompletionCancel(Sender: TObject); begin //debugln(['TCompOptBuildMacrosFrame.fSynCompletionCancel ',fSynCompletion.TheForm.Visible]); if fSynCompletion.TheForm.Visible then fSynCompletion.Deactivate; fSynCompletion.RemoveEditor(CondSynEdit); end; procedure TCompOptBuildMacrosFrame.fSynCompletionExecute(Sender: TObject); begin //debugln(['TCompOptBuildMacrosFrame.fSynCompletionExecute ']); end; procedure TCompOptBuildMacrosFrame.fSynCompletionKeyCompletePrefix( Sender: TObject); begin //debugln(['TCompOptBuildMacrosFrame.fSynCompletionKeyCompletePrefix ToDo']); end; procedure TCompOptBuildMacrosFrame.fSynCompletionKeyDelete(Sender: TObject); begin //debugln(['TCompOptBuildMacrosFrame.fSynCompletionKeyDelete']); end; procedure TCompOptBuildMacrosFrame.fSynCompletionKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin //debugln(['TCompOptBuildMacrosFrame.fSynCompletionKeyDown ']); if Key=VK_BACK then begin Key:=VK_UNKNOWN; if fSynCompletion.CurrentString<>'' then CondSynEdit.CommandProcessor(ecDeleteLastChar,#0,nil); end; end; procedure TCompOptBuildMacrosFrame.fSynCompletionKeyNextChar(Sender: TObject); var XY: TPoint; StartX: integer; EndX: integer; Line: string; begin XY:=CondSynEdit.LogicalCaretXY; if XY.Y>CondSynEdit.Lines.Count then exit; CondSynEdit.GetWordBoundsAtRowCol(XY,StartX,EndX); if EndX<=XY.X then exit; Line := CondSynEdit.Lines[XY.Y - 1]; inc(XY.X,UTF8CharacterLength(@Line[XY.X-1])); CondSynEdit.LogicalCaretXY:=XY; end; procedure TCompOptBuildMacrosFrame.fSynCompletionKeyPrevChar(Sender: TObject); var XY: TPoint; StartX: integer; EndX: integer; Line: string; begin XY:=CondSynEdit.LogicalCaretXY; if XY.Y>CondSynEdit.Lines.Count then exit; CondSynEdit.GetWordBoundsAtRowCol(XY,StartX,EndX); if StartX>=XY.X then exit; Line := CondSynEdit.Lines[XY.Y - 1]; XY.X:=UTF8FindNearestCharStart(PChar(Line),length(Line),XY.X-2)+1; CondSynEdit.LogicalCaretXY:=XY; end; procedure TCompOptBuildMacrosFrame.fSynCompletionSearchPosition( var Position: integer); var sl: TStringList; Prefix: String; s: string; i: Integer; begin //debugln(['TCompOptBuildMacrosFrame.fSynCompletionSearchPosition "',fSynCompletion.CurrentString,'"']); Prefix:=fSynCompletion.CurrentString; sl:=TStringList.Create; try Position:=-1; for i:=0 to CompletionValues.Count-1 do begin s:=CompletionValues[i]; if SysUtils.CompareText(Prefix,copy(s,1,length(Prefix)))<>0 then continue; if (Position<0) or (length(Prefix)=length(s)) then Position:=sl.Count; sl.AddObject(s,TObject({%H-}Pointer(i))); end; fSynCompletion.ItemList.Assign(sl); finally sl.Free; end; end; procedure TCompOptBuildMacrosFrame.fSynCompletionUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char); begin //debugln(['TCompOptBuildMacrosFrame.fSynCompletionUTF8KeyPress ']); end; procedure TCompOptBuildMacrosFrame.fSynCompletionValidate(Sender: TObject; KeyChar: TUTF8Char; Shift: TShiftState); var i: LongInt; s: string; p: LongInt; TxtXY: TPoint; TxtStartX: integer; TxtEndX: integer; begin //debugln(['TCompOptBuildMacrosFrame.fSynCompletionValidate ']); i:=fSynCompletion.Position; if (i>=0) and (i=0) and (i0 then s:=copy(s,1,p-1); TxtXY:=CondSynEdit.LogicalCaretXY; CondSynEdit.GetWordBoundsAtRowCol(TxtXY,TxtStartX,TxtEndX); CondSynEdit.BeginUndoBlock{$IFDEF SynUndoDebugBeginEnd}('TCompOptBuildMacrosFrame.fSynCompletionValidate'){$ENDIF}; try CondSynEdit.BlockBegin:=Point(TxtStartX,TxtXY.Y); CondSynEdit.BlockEnd:=Point(TxtEndX,TxtXY.Y); CondSynEdit.SelText:=s; finally CondSynEdit.EndUndoBlock{$IFDEF SynUndoDebugBeginEnd}('TCompOptBuildMacrosFrame.fSynCompletionValidate'){$ENDIF}; end; FCompletionHistory.Insert(0,s); if FCompletionHistory.Count>100 then FCompletionHistory.Delete(FCompletionHistory.Count-1); end; end; fSynCompletion.Deactivate; end; procedure TCompOptBuildMacrosFrame.BuildMacrosTreeViewEditing(Sender: TObject; Node: TTreeNode; var AllowEdit: Boolean); var BuildProperty: TLazBuildMacro; NodeType: TCBMNodeType; begin NodeType:=GetNodeInfo(Node,BuildProperty); AllowEdit:=NodeType in [cbmntBuildMacro,cbmntValue]; end; procedure TCompOptBuildMacrosFrame.BuildMacrosTreeViewSelectionChanged(Sender: TObject); begin UpdateItemPropertyControls; end; procedure TCompOptBuildMacrosFrame.BuildMacrosTreeViewEdited(Sender: TObject; Node: TTreeNode; var S: string); var BuildMacro: TLazBuildMacro; NodeType: TCBMNodeType; ConflictBuildProperty: TIDEBuildMacro; Index: LongInt; Prefix: String; BetterName: String; DlgResult: TModalResult; Vars: TCTCfgScriptVariables; ok: Boolean; begin NodeType:=GetNodeInfo(Node,BuildMacro); case NodeType of cbmntBuildMacro: if S<>BuildMacro.Identifier then begin // rename build macro ok:=false; try // check syntax if (S='') or (not IsValidIdent(S)) then begin MessageDlg(lisCCOErrorCaption, Format(lisInvalidMacroTheMacroMustBeAPascalIdentifie, ['"', S, '"']), mtError,[mbCancel],0); exit; end; // check for prefix Prefix:=GetMacroNamePrefix(cbmpShort); if (Prefix<>'') and (SysUtils.CompareText(Prefix,copy(S,1,length(Prefix)))<>0) then begin BetterName:=GetMacroNamePrefix(cbmpMedium)+S; DlgResult:=IDEQuestionDialog(lisCCOWarningCaption, Format(lisTheMacroDoesNotBeginWith, [S, Prefix]), mtWarning, [mrCancel, mrYes, Format(lisRenameTo, [BetterName]), mrIgnore]); if DlgResult=mrIgnore then begin end else if DlgResult=mrYes then S:=BetterName else begin exit; end; end; // check for keyword if WordIsKeyWord.DoItCaseInsensitive(S) then begin MessageDlg(lisCCOErrorCaption, Format(lisInvalidMacroTheNameIsAKeyword, [S]), mtError,[mbCancel],0); exit; end; // check for conflicts with built-in macros if IDEMacros.IsMacro(s) then begin MessageDlg(lisCCOErrorCaption, Format(lisThereIsAlreadyAnIDEMacroWithTheName, [S]), mtError,[mbCancel],0); exit; end; // check for duplicates ConflictBuildProperty:=BuildMacros.VarWithIdentifier(S); if ((ConflictBuildProperty<>nil) and (ConflictBuildProperty<>BuildMacro)) or (SysUtils.CompareText('TargetOS',S)=0) or (SysUtils.CompareText('SrcOS',S)=0) or (SysUtils.CompareText('SrcOS2',S)=0) or (SysUtils.CompareText('TargetCPU',S)=0) or (SysUtils.CompareText('LCLWidgetType',S)=0) then begin MessageDlg(lisCCOErrorCaption, Format(lisThereIsAlreadyAMacroWithTheName, ['"', S, '"']), mtError,[mbCancel],0); exit; end; // check for duplicates with used packages if (BuildMacros<>nil) and (BuildMacros.Owner is TBaseCompilerOptions) then begin Vars:=GetBuildMacroValues(TBaseCompilerOptions(BuildMacros.Owner),false); if (Vars<>nil) and Vars.IsDefined(PChar(S)) then begin DlgResult:=MessageDlg(lisCCOWarningCaption, Format(lisThereIsAlreadyAMacroWithTheName, ['"', S, '"']), mtWarning,[mbCancel,mbIgnore],0); if DlgResult<>mrIgnore then exit; end; end; ok:=true; finally if not ok then S:=BuildMacro.Identifier; end; // rename build macro BuildMacro.Identifier:=S; end; cbmntValue: begin Index:=Node.Index; Index:=BuildMacro.Values.IndexOf(S); if (Index>=0) and (Index<>Node.Index) then begin MessageDlg(lisCCOErrorCaption, Format(lisDuplicateFoundOfValue, ['"', S, '"']), mtError,[mbCancel],0); S:=BuildMacro.Values[Node.Index]; exit; end; BuildMacro.Values[Node.Index]:=S; end; end; end; procedure TCompOptBuildMacrosFrame.BMAddMacroSpeedButtonClick(Sender: TObject); var NewIdentifier: String; NewBuildMacro: TLazBuildMacro; i: Integer; TVNode: TTreeNode; begin //debugln(['TCompOptBuildMacrosFrame.BMAddMacroSpeedButtonClick ',GetMacroNamePrefix(cbmpLong)]); i:=1; repeat NewIdentifier:=GetMacroNamePrefix(cbmpLong)+IntToStr(BuildMacros.Count+1); if BuildMacros.IndexOfIdentifier(NewIdentifier)<0 then break; inc(i); until false; NewBuildMacro:=BuildMacros.Add(NewIdentifier); // add to TreeView BuildMacrosTreeView.BeginUpdate; TVNode:=TreeViewAddBuildMacro(NewBuildMacro); BuildMacrosTreeView.Selected:=TVNode; BuildMacrosTreeView.EndUpdate; UpdateItemPropertyControls; end; procedure TCompOptBuildMacrosFrame.BMAddMacroValueSpeedButtonClick( Sender: TObject); var BuildMacro: TLazBuildMacro; NodeType: TCBMNodeType; i: Integer; NewValueStr: String; ValuesTVNode: TTreeNode; begin GetSelectedNode(BuildMacro,NodeType); if BuildMacro=nil then exit; i:=1; repeat NewValueStr:=Format(lisValue2, [IntToStr(i)]); if BuildMacro.Values.IndexOf(NewValueStr)<0 then break; inc(i); until false; BuildMacro.Values.Add(NewValueStr); BuildMacrosTreeView.BeginUpdate; ValuesTVNode:=GetBuildMacroTVNode(BuildMacro); TreeViewAddValue(ValuesTVNode,NewValueStr); ValuesTVNode.Expand(true); BuildMacrosTreeView.EndUpdate; end; procedure TCompOptBuildMacrosFrame.BMDeleteMacroSpeedButtonClick(Sender: TObject); var aBuildMacro: TIDEBuildMacro; SelTVNode: TTreeNode; NodeType: TCBMNodeType; i: LongInt; aValue: String; begin SelTVNode:=GetSelectedNode(TLazBuildMacro(aBuildMacro),NodeType); if aBuildMacro=nil then exit; if NodeType=cbmntValue then begin aValue:=SelTVNode.Text; if MessageDlg(lisConfirmDelete, Format(lisDeleteValue, ['"', aValue, '"']), mtConfirmation,[mbYes,mbCancel],0)<>mrYes then exit; i:=aBuildMacro.Values.IndexOf(aValue); if i>=0 then aBuildMacro.Values.Delete(i); end else begin if MessageDlg(lisConfirmDelete, Format(lisDeleteMacro, ['"', aBuildMacro.Identifier, '"']), mtConfirmation,[mbYes,mbCancel],0)<>mrYes then exit; i:=BuildMacros.IndexOfIdentifier(aBuildMacro.Identifier); BuildMacros.Delete(i); end; BuildMacrosTreeView.BeginUpdate; SelTVNode.Delete; BuildMacrosTreeView.EndUpdate; end; procedure TCompOptBuildMacrosFrame.BuildMacroDescriptionEditExit(Sender: TObject); var BuildMacro: TLazBuildMacro; NodeType: TCBMNodeType; begin GetSelectedNode(BuildMacro,NodeType); if BuildMacro<>nil then BuildMacro.Description:=BuildMacroDescriptionEdit.Text; end; procedure TCompOptBuildMacrosFrame.SetBuildMacros( const AValue: TIDEBuildMacros); begin if FBuildMacros=AValue then exit; if AValue<>nil then begin BuildMacros.Assign(AValue); FMacrosOwner:=AValue.Owner; end else begin FMacrosOwner:=nil; end; RebuildTreeView; UpdateItemPropertyControls; IdleConnected:=true; end; procedure TCompOptBuildMacrosFrame.RebuildTreeView; var i: Integer; begin BuildMacrosTreeView.BeginUpdate; BuildMacrosTreeView.Items.Clear; if BuildMacros<>nil then begin // first level: build macros for i:=0 to BuildMacros.Count-1 do TreeViewAddBuildMacro(BuildMacros.Items[i]); end; BuildMacrosTreeView.EndUpdate; end; procedure TCompOptBuildMacrosFrame.SetIdleConnected(const AValue: Boolean); begin if FIdleConnected=AValue then exit; FIdleConnected:=AValue; if FIdleConnected then Application.AddOnIdleHandler(@OnIdle) else Application.RemoveOnIdleHandler(@OnIdle); end; procedure TCompOptBuildMacrosFrame.SetStatusMessage(const AValue: string); begin if FStatusMessage=AValue then exit; FStatusMessage:=AValue; CondStatusbar.Panels[2].Text := FStatusMessage; end; function TCompOptBuildMacrosFrame.TreeViewAddBuildMacro( aBuildMacro: TLazBuildMacro): TTreeNode; var Values: TStrings; i: Integer; begin // create node for the build macro Result:=BuildMacrosTreeView.Items.AddObject(nil,aBuildMacro.Identifier,aBuildMacro); Result.ImageIndex:=fVarImgID; Result.SelectedIndex:=Result.ImageIndex; // second level begin // a node for each value Values:=aBuildMacro.Values; for i:=0 to Values.Count-1 do TreeViewAddValue(Result,Values[i]); end; //DebugLn(['TCompOptBuildMacrosFrame.TreeViewAddBuildMacro ',TVNode.Text]); Result.Expand(true); end; procedure TCompOptBuildMacrosFrame.TreeViewAddValue(ValuesTVNode: TTreeNode; aValue: string); var ValueTVNode: TTreeNode; begin ValueTVNode:=BuildMacrosTreeView.Items.AddChild(ValuesTVNode,aValue); ValueTVNode.ImageIndex:=fValueImgID; ValueTVNode.SelectedIndex:=ValueTVNode.ImageIndex; end; function TCompOptBuildMacrosFrame.GetNodeInfo(Node: TTreeNode; out BuildMacro: TLazBuildMacro): TCBMNodeType; function GetNodeType(CurNode: TTreeNode): TCBMNodeType; var ParentType: TCBMNodeType; begin if CurNode=nil then Result:=cbmntNone else if TObject(CurNode.Data) is TLazBuildMacro then begin BuildMacro:=TLazBuildMacro(CurNode.Data); Result:=cbmntBuildMacro; end else begin ParentType:=GetNodeType(CurNode.Parent); case ParentType of cbmntBuildMacro: Result:=cbmntValue; end; end; end; begin BuildMacro:=nil; Result:=GetNodeType(Node); end; function TCompOptBuildMacrosFrame.GetSelectedNode(out aBuildMacro: TLazBuildMacro; out NodeType: TCBMNodeType): TTreeNode; begin Result:=BuildMacrosTreeView.Selected; NodeType:=GetNodeInfo(Result,aBuildMacro); end; function TCompOptBuildMacrosFrame.GetBuildMacroTVNode(aBuildMacro: TLazBuildMacro ): TTreeNode; begin Result:=BuildMacrosTreeView.Items.GetFirstNode; while (Result<>nil) and (TObject(Result.Data)<>aBuildMacro) do Result:=Result.GetNextSibling; end; function TCompOptBuildMacrosFrame.GetMacroNamePrefix(PrefixType: TCBMPrefixType ): string; begin if PrefixType=cbmpShort then Result:='' else Result:='BuildMacro'; if (MacrosOwner=nil) then exit; if MacrosOwner is TPkgCompilerOptions then begin Result:=TPkgCompilerOptions(MacrosOwner).LazPackage.Name; if ord(PrefixType)>=ord(cbmpMedium) then Result:=Result+'_'; if PrefixType=cbmpLong then Result:=Result+'macro'; end; end; procedure TCompOptBuildMacrosFrame.UpdateItemPropertyControls; var aBuildMacro: TLazBuildMacro; NodeType: TCBMNodeType; begin GetSelectedNode(aBuildMacro,NodeType); if aBuildMacro<>nil then begin BuildMacroSelectedGroupBox.Caption:=Format(lisMacro, [aBuildMacro.Identifier]); BuildMacroSelectedGroupBox.Enabled:=true; BuildMacroDescriptionEdit.Enabled:=true; BuildMacroDescriptionEdit.Text:=aBuildMacro.Description; BMAddMacroValueSpeedButton.Hint:=Format(lisAddValueToMacro, [aBuildMacro.Identifier]); if NodeType=cbmntBuildMacro then BMDeleteMacroSpeedButton.Hint:=Format(lisDeleteMacro, ['"',aBuildMacro.Identifier,'"']) else BMDeleteMacroSpeedButton.Hint:=Format(lisDeleteValue2, [BuildMacrosTreeView. Selected.Text]); end else begin BuildMacroSelectedGroupBox.Caption:=lisNoMacroSelected; BuildMacroSelectedGroupBox.Enabled:=false; BuildMacroDescriptionEdit.Enabled:=false; BuildMacroDescriptionEdit.Text:=''; BMAddMacroValueSpeedButton.Hint:=''; BMDeleteMacroSpeedButton.Hint:=''; end; BMAddMacroSpeedButton.Hint:=lisAddNewMacro; BMAddMacroValueSpeedButton.Enabled:=NodeType in [cbmntBuildMacro,cbmntValue]; BMDeleteMacroSpeedButton.Enabled:=NodeType in [cbmntBuildMacro,cbmntValue]; end; procedure TCompOptBuildMacrosFrame.UpdateMessages; begin fEngine.Variables.Assign(DefaultVariables); fEngine.Execute(CondSynEdit.Lines.Text,1); if fEngine.ErrorCount>0 then begin StatusMessage:=fEngine.GetErrorStr(0); end else begin StatusMessage:=lisNoErrors; end; end; procedure TCompOptBuildMacrosFrame.UpdateStatusBar; var PanelCharMode: String; PanelXY: String; begin PanelXY := Format(' %6d:%4d',[CondSynEdit.CaretY,CondSynEdit.CaretX]); if CondSynEdit.InsertMode then PanelCharMode := uepIns else PanelCharMode := uepOvr; CondStatusbar.Panels[0].Text := PanelXY; CondStatusbar.Panels[1].Text := PanelCharMode; end; procedure TCompOptBuildMacrosFrame.StartCompletion; function EditorRowColumnToCompletionXY(ScreenRowCol: TPoint; AboveRow: boolean): TPoint; begin if not AboveRow then inc(ScreenRowCol.Y,1); Result:=CondSynEdit.RowColumnToPixels(ScreenRowCol); Result:=CondSynEdit.ClientToScreen(Result); if fSynCompletion.TheForm.Parent<>nil then Result:=fSynCompletion.TheForm.Parent.ScreenToClient(Result); end; var LogStartX: integer; LogEndX: integer; LogXY: TPoint; ScreenXY: TPoint; XY: TPoint; Line: String; begin //debugln(['TCompOptBuildMacrosFrame.StartCompletion ']); UpdateCompletionValues; fSynCompletion.ItemList.Assign(CompletionValues); // get row and column of word start at cursor LogXY:=CondSynEdit.LogicalCaretXY; CondSynEdit.GetWordBoundsAtRowCol(LogXY,LogStartX,LogEndX); LogEndX:=Min(LogEndX,LogXY.X); // convert text row,column to screen row,column ScreenXY:=CondSynEdit.PhysicalToLogicalPos(Point(LogStartX,LogXY.Y)); // convert screen row,column to coordinates for the completion form XY:=EditorRowColumnToCompletionXY(ScreenXY,false); if XY.Y+fSynCompletion.TheForm.Height>fSynCompletion.TheForm.Parent.ClientHeight then begin // place completion above text XY:=EditorRowColumnToCompletionXY(ScreenXY,true); dec(XY.Y,fSynCompletion.TheForm.Height); end; // show completion box fSynCompletion.AddEditor(CondSynEdit); Line:=CondSynEdit.LineText; fSynCompletion.Execute(copy(Line,LogStartX,LogEndX-LogStartX),XY.X,XY.Y); end; procedure TCompOptBuildMacrosFrame.UpdateCompletionValues; function HasWord(const aName: string): Boolean; var i: Integer; s: string; p: LongInt; begin for i:=0 to CompletionValues.Count-1 do begin s:=CompletionValues[i]; p:=System.Pos(#9,s); if p>0 then s:=copy(s,1,p-1); if SysUtils.CompareText(s,aName)=0 then exit(true); end; Result:=false; end; procedure AddKeyword(aName: string); begin CompletionValues.Add(aName); end; procedure AddWord(aName: string); begin aName:=dbgstr(aName); if aName='' then exit; if HasWord(aName) then exit; CompletionValues.Add(aName); end; procedure AddVar(aName, aValue: string); var s: String; begin aName:=dbgstr(aName); if aName='' then exit; if HasWord(aName) then exit; s:=dbgstr(aValue); if length(s)>50 then s:=copy(s,1,50)+'...'; s:=aName+#9+aValue; CompletionValues.Add(s); end; var Node: TAVLTreeNode; V: PCTCfgScriptVariable; s: String; p: PChar; AtomStart: PChar; Macro: TLazBuildMacro; pcov: TParsedCompilerOptString; pcouv: TParsedCompilerOptString; i: Integer; j: Integer; begin CompletionValues.Clear; // add default variables with values Node:=DefaultVariables.Tree.FindLowest; while Node<>nil do begin V:=PCTCfgScriptVariable(Node.Data); AddVar(V^.Name,GetCTCSVariableAsString(V)); Node:=DefaultVariables.Tree.FindSuccessor(Node); end; // add keywords and operands AddKeyword('if'); AddKeyword('then'); AddKeyword('else'); AddKeyword('begin'); AddKeyword('end'); AddKeyword('not'); AddKeyword('and'); AddKeyword('or'); AddKeyword('xor'); AddKeyword('undefine'); AddKeyword('defined'); AddKeyword('undefined'); AddKeyword('integer'); AddKeyword('int64'); AddKeyword('string'); AddKeyword('true'); AddKeyword('false'); // add IDE functions AddWord('GetIDEValue(''OS'')'); AddWord('GetIDEValue(''CPU'')'); AddWord('GetIDEValue(''SrcOS'')'); AddWord('GetIDEValue(''SrcOS2'')'); AddWord('GetIDEValue(''LCLWidgetType'')'); AddWord('GetEnv(''USER'')'); AddWord('GetEnv(''HOME'')'); // add result variables for pcov:=low(ParsedCompilerOptsVars) to high(ParsedCompilerOptsVars) do AddWord(ParsedCompilerOptsVars[pcov]); if FIsPackage then for pcouv:=low(ParsedCompilerOptsUsageVars) to high(ParsedCompilerOptsUsageVars) do AddWord(ParsedCompilerOptsUsageVars[pcouv]); // add build macros and values if BuildMacros<>nil then begin for i:=0 to BuildMacros.Count-1 do begin Macro:=BuildMacros[i]; AddWord(Macro.Identifier); for j:=0 to Macro.Values.Count-1 do AddWord(Macro.Values[j]); end; end; // add words in text s:=CondSynEdit.Lines.Text; if s<>'' then begin p:=PChar(s); repeat AtomStart:=p; while (AtomStart^<>#0) and not IsIdentStartChar[AtomStart^] do inc(AtomStart); if (AtomStart^=#0) then break; p:=AtomStart; while IsIdentChar[p^] do inc(p); AddWord(copy(s,AtomStart-PChar(s)+1,p-AtomStart)); until false; end; // sort alphabetically TStringList(FCompletionValues).Sort; // push recently used words upwards for i:=CompletionHistory.Count-1 downto 0 do begin j:=CompletionValues.IndexOf(CompletionHistory[i]); if j>0 then CompletionValues.Move(j,0); end; // set index for i:=0 to CompletionValues.Count-1 do CompletionValues.Objects[i]:=TObject({%H-}Pointer(i)); //debugln(['TCompOptBuildMacrosFrame.UpdateCompletionValues ',CompletionValues.Text]); end; function TCompOptBuildMacrosFrame.GetCondCursorWord: string; var XY: TPoint; StartX: integer; EndX: integer; Line: string; begin XY:=CondSynEdit.LogicalCaretXY; if (XY.Y>=1) and (XY.Y<=CondSynEdit.Lines.Count) then begin CondSynEdit.GetWordBoundsAtRowCol(XY,StartX,EndX); //debugln(['TCompOptBuildMacrosFrame.GetCondCursorWord ',StartX,' ',EndX,' ',XY.X]); EndX:=Min(EndX,XY.X); Line := CondSynEdit.Lines[XY.Y - 1]; Result:= Copy(Line, StartX, EndX - StartX); end else Result:=''; //debugln(['TCompOptBuildMacrosFrame.GetCondCursorWord "',Result,'"']); end; constructor TCompOptBuildMacrosFrame.Create(TheOwner: TComponent); begin inherited Create(TheOwner); FCompletionValues:=TStringList.Create; FCompletionHistory:=TStringList.Create; fDefaultVariables:=TCTCfgScriptVariables.Create; FBuildMacros:=TIDEBuildMacros.Create(nil); fEngine:=TIDECfgScriptEngine.Create; MacrosGroupBox.Caption:=lisIDEMacros; BuildMacrosTreeView.Images := IDEImages.Images_24; fVarImgID:=IDEImages.LoadImage(24,'da_define'); fValueImgID:=IDEImages.LoadImage(24,'da_define'); fDefValueImgID:=IDEImages.LoadImage(24,'da_define'); BuildMacroDefaultLabel.Caption:= lisHintADefaultValueCanBeDefinedInTheConditionals; BuildMacroDescriptionLabel.Caption:=lisCodeToolsDefsDescription; ConditionalsGroupBox.Caption:=lisConditionals; CondSynEdit.OnStatusChange:=@CondSynEditStatusChange; BMAddMacroSpeedButton.LoadGlyphFromLazarusResource('laz_add'); BMAddMacroValueSpeedButton.LoadGlyphFromLazarusResource('laz_add'); BMDeleteMacroSpeedButton.LoadGlyphFromLazarusResource('laz_delete'); fSynCompletion:=TSynCompletion.Create(Self); fSynCompletion.TheForm.Parent:=Self; fSynCompletion.OnExecute:=@fSynCompletionExecute; fSynCompletion.OnCancel:=@fSynCompletionCancel; fSynCompletion.OnValidate:=@fSynCompletionValidate; fSynCompletion.OnSearchPosition:=@fSynCompletionSearchPosition; fSynCompletion.OnKeyCompletePrefix:=@fSynCompletionKeyCompletePrefix; fSynCompletion.OnUTF8KeyPress:=@fSynCompletionUTF8KeyPress; fSynCompletion.OnKeyNextChar:=@fSynCompletionKeyNextChar; fSynCompletion.OnKeyPrevChar:=@fSynCompletionKeyPrevChar; fSynCompletion.OnKeyDelete:=@fSynCompletionKeyDelete; fSynCompletion.OnKeyDown:=@fSynCompletionKeyDown; end; destructor TCompOptBuildMacrosFrame.Destroy; begin FreeAndNil(FCompletionHistory); FreeAndNil(FCompletionValues); FreeAndNil(fDefaultVariables); FreeAndNil(fEngine); FreeAndNil(FBuildMacros); inherited Destroy; end; function TCompOptBuildMacrosFrame.GetTitle: String; begin Result:=lisIDEMacros; end; procedure TCompOptBuildMacrosFrame.ReadSettings(AOptions: TAbstractIDEOptions); begin if AOptions is TBaseCompilerOptions then LoadFromOptions(TBaseCompilerOptions(AOptions)); end; procedure TCompOptBuildMacrosFrame.Setup(ADialog: TAbstractOptionsEditorDialog); begin end; class function TCompOptBuildMacrosFrame.SupportedOptionsClass: TAbstractIDEOptionsClass; begin Result := TBaseCompilerOptions; end; procedure TCompOptBuildMacrosFrame.WriteSettings(AOptions: TAbstractIDEOptions); begin if AOptions is TBaseCompilerOptions then SaveToOptions(TBaseCompilerOptions(AOptions)); end; procedure TCompOptBuildMacrosFrame.LoadFromOptions(Options: TBaseCompilerOptions); var Vars: TCTCfgScriptVariables; begin FIsPackage:=Options is TPkgCompilerOptions; BuildMacros:=Options.BuildMacros as TIDEBuildMacros; Vars:=GetBuildMacroValues(Options,false); if Vars<>nil then DefaultVariables.Assign(Vars) else DefaultVariables.Clear; CondSynEdit.Lines.Text:=Options.Conditionals; EditorOpts.GetSynEditSettings(CondSynEdit); if FHighlighter=nil then begin FHighlighter := TPreviewPasSyn.Create(Self); CondSynEdit.Highlighter:=FHighlighter; end; EditorOpts.ReadHighlighterSettings(FHighlighter, ''); UpdateStatusBar; end; procedure TCompOptBuildMacrosFrame.SaveToOptions(Options: TBaseCompilerOptions); begin (Options.BuildMacros as TIDEBuildMacros).Assign(BuildMacros); Options.Conditionals:=CondSynEdit.Lines.Text; end; initialization RegisterIDEOptionsEditor(GroupCompiler, TCompOptBuildMacrosFrame, CompilerOptionsConditional); RegisterIDEOptionsEditor(GroupPkgCompiler, TCompOptBuildMacrosFrame, CompilerOptionsConditional); end.