{ *************************************************************************** * * * 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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: Editor dialog to edit nodes of Help for IDE windows (controls). } {%RunCommand bb$MakeExe($(EdFile))} unit IDEContextHelpEdit; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LCLProc, Forms, Controls, Graphics, Dialogs, Buttons, ButtonPanel, StdCtrls, ComCtrls, // codetools CodeToolManager, CodeCache, // IDEIntf PropEdits, LazIDEIntf, IDEWindowIntf, IDEDialogs, // IDE IDEWindowHelp, LazarusIDEStrConsts, ExtCtrls; type { TOpenIDEFileOnIdle } TOpenIDEFileOnIdle = class(TComponent) protected procedure Notification(AComponent: TComponent; Operation: TOperation); override; public Filename: string; X, Y: integer; CloseDialogs: TFPList; // list of modal TCustomForm to cancel CancelDialogs: TFPList; // list of modal TCustomForm tried to cancel Ending: boolean; constructor Create(AOwner: TComponent); override; procedure Run; destructor Destroy; override; procedure OnIdle(Sender: TObject; var {%H-}Done: Boolean); end; { TContextHelpEditorDlg } TContextHelpEditorDlg = class(TForm) ButtonPanel: TButtonPanel; FullPathEdit: TEdit; NodeIsRootCheckBox: TCheckBox; OpenDeclarationBitBtn: TBitBtn; TestButton: TButton; CreateHelpNodeForControlButton: TButton; NodeNameEdit: TEdit; NodePathEdit: TEdit; NodeHasHelpCheckBox: TCheckBox; HelpNodePropertiesGroupBox: TGroupBox; NodePathLabel: TLabel; NodeNameLabel: TLabel; NodesGroupBox: TGroupBox; ControlsTreeView: TTreeView; HelpNodesTreeView: TTreeView; Splitter1: TSplitter; WindowControlsGroupBox: TGroupBox; procedure ControlsTreeViewShowHint(Sender: TObject; HintInfo: PHintInfo); procedure CreateHelpNodeForControlButtonClick(Sender: TObject); procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure HelpNodesTreeViewSelectionChanged(Sender: TObject); procedure NodeHasHelpCheckBoxEditingDone(Sender: TObject); procedure NodeIsRootCheckBoxEditingDone(Sender: TObject); procedure NodeNameEditEditingDone(Sender: TObject); procedure NodePathEditEditingDone(Sender: TObject); procedure OkBitBtnClick(Sender: TObject); procedure OpenDeclarationBitBtnClick(Sender: TObject); procedure OpenDeclarationBitBtnShowHint(Sender: TObject; HintInfo: PHintInfo); procedure TestButtonClick(Sender: TObject); private FIDEWindow: TCustomForm; FInvoker: TObject; FWorkingHelpNodes: TIWHelpTree; fLoading: boolean; procedure SetIDEWindow(const AValue: TCustomForm); procedure SetInvoker(const AValue: TObject); procedure UpdateWindowControlsGroupBoxCaption; procedure FillControlsTreeView; procedure FillHelpNodesTreeView; procedure UpdateHelpNodePropertiesGroupBox; procedure SelectHelpNode(AControl: TControl); procedure SelectControlNode(AControl: TControl); function FindHelpTreeNode(HelpNode: TIWHelpNode): TTreeNode; function FindControlTreeNode(AControl: TControl): TTreeNode; function GetCurrentControl: TControl; function GetCurrentHelpNode: TIWHelpNode; procedure SaveHelpNodeProperties; function GetHintForControl(AControl: TControl): string; public property Invoker: TObject read FInvoker write SetInvoker; property IDEWindow: TCustomForm read FIDEWindow write SetIDEWindow; property WorkingHelpNodes: TIWHelpTree read FWorkingHelpNodes; end; var ContextHelpEditorDlg: TContextHelpEditorDlg = nil; function ShowContextHelpEditor(Sender: TObject): TModalResult; function FindDeclarationOfIDEControl(AControl: TControl; out Filename: string; out X, Y: integer): boolean; implementation {$R *.lfm} function ShowContextHelpEditor(Sender: TObject): TModalResult; begin // make sure there is only one editor at a time if ContextHelpEditorDlg<>nil then exit; ContextHelpEditorDlg:=TContextHelpEditorDlg.Create(nil); try ContextHelpEditorDlg.Invoker:=Sender; Result:=ContextHelpEditorDlg.ShowModal; finally ContextHelpEditorDlg.Free; ContextHelpEditorDlg:=nil; end; end; function FindDeclarationOfIDEControl(AControl: TControl; out Filename: string; out X, Y: integer): boolean; var UnitControl: TControl; FormFilename: String; Code: TCodeBuffer; TopLine: integer; NewCode: TCodeBuffer; Path: String; AComponent: TComponent; begin Result:=false; Filename:=''; Y:=0; X:=0; if AControl=nil then exit; UnitControl:=AControl; while (UnitControl<>nil) do begin if (UnitControl is TFrame) or (UnitControl is TCustomForm) then break; UnitControl:=UnitControl.Parent; end; if UnitControl=nil then begin debugln(['FindDeclarationOfIDEControl control '+DbgSName(AControl)+' is not on a form/frame']); exit; end; //debugln(['FindDeclarationOfIDEControl UnitControl=',DbgSName(UnitControl),' Unitname=',UnitControl.UnitName]); FormFilename:=LazarusIDE.FindUnitFile(GetClassUnitName(UnitControl.ClassType),LazarusIDE); //debugln(['FindDeclarationOfIDEControl FormFilename=',FormFilename]); if FormFilename='' then begin debugln(['FindDeclarationOfIDEControl UnitControl=',DbgSName(UnitControl),' Unitname=',GetClassUnitName(UnitControl.ClassType),': unit source not found']); exit; end; Code:=CodeToolBoss.LoadFile(FormFilename,true,false); if Code=nil then begin debugln(['FindDeclarationOfIDEControl unable to open file: '+FormFilename]); exit; end; Path:=''; AComponent:=AControl; while (AComponent<>nil) and (AComponent<>UnitControl) do begin Path:='.'+AComponent.Name+Path; AComponent:=AComponent.Owner; end; Path:=UnitControl.ClassName+Path; if not CodeToolBoss.FindDeclarationOfPropertyPath(Code,Path,NewCode,X,Y,TopLine) then begin debugln(['FindDeclarationOfIDEControl path ',Path,' not found in unit ',Code.Filename]); exit; end; Filename:=NewCode.Filename; // success Result:=true; end; { TContextHelpEditorDlg } procedure TContextHelpEditorDlg.FormClose(Sender: TObject; var CloseAction: TCloseAction); begin IDEDialogLayoutList.SaveLayout(Self); end; procedure TContextHelpEditorDlg.CreateHelpNodeForControlButtonClick( Sender: TObject); var AControl: TControl; begin AControl:=GetCurrentControl; if AControl=nil then exit; WorkingHelpNodes.FindNodeForControl(AControl,true); FillHelpNodesTreeView; SelectHelpNode(AControl); SelectControlNode(AControl); end; procedure TContextHelpEditorDlg.ControlsTreeViewShowHint(Sender: TObject; HintInfo: PHintInfo); var Node: TTreeNode; begin Node:=ControlsTreeView.GetNodeAt(HintInfo^.CursorPos.X,HintInfo^.CursorPos.Y); if Node=nil then exit; HintInfo^.HintStr:=GetHintForControl(TControl(Node.Data)); end; procedure TContextHelpEditorDlg.FormCreate(Sender: TObject); begin Caption:=lisEditContextHelp; TestButton.Caption:=dlgCCOTest; CreateHelpNodeForControlButton.Caption:=lisCreateHelpNode; OpenDeclarationBitBtn.Caption:=lisDlgOpen; OpenDeclarationBitBtn.OnShowHint:=@OpenDeclarationBitBtnShowHint; NodeHasHelpCheckBox.Caption:=lisHasHelp; NodeIsRootCheckBox.Caption:=lisCEIsARootControl; NodePathLabel.Caption:=lisPath; NodeNameLabel.Caption:=lisName; NodesGroupBox.Caption:=lisHelpEntries; ButtonPanel.OKButton.OnClick:=@OkBitBtnClick; IDEDialogLayoutList.ApplyLayout(Self, 600, 450); LoadIDEWindowHelp; FWorkingHelpNodes:=TIWHelpTree.Create; FWorkingHelpNodes.Assign(IDEWindowHelpNodes); FillHelpNodesTreeView; end; procedure TContextHelpEditorDlg.FormDestroy(Sender: TObject); begin FreeAndNil(FWorkingHelpNodes); end; procedure TContextHelpEditorDlg.HelpNodesTreeViewSelectionChanged( Sender: TObject); begin UpdateHelpNodePropertiesGroupBox; end; procedure TContextHelpEditorDlg.NodeHasHelpCheckBoxEditingDone(Sender: TObject); begin SaveHelpNodeProperties; end; procedure TContextHelpEditorDlg.NodeIsRootCheckBoxEditingDone(Sender: TObject); begin SaveHelpNodeProperties; end; procedure TContextHelpEditorDlg.NodeNameEditEditingDone(Sender: TObject); begin SaveHelpNodeProperties; end; procedure TContextHelpEditorDlg.NodePathEditEditingDone(Sender: TObject); begin SaveHelpNodeProperties; end; procedure TContextHelpEditorDlg.OkBitBtnClick(Sender: TObject); begin WorkingHelpNodes.DeleteLeavesWithoutHelp; IDEWindowHelpNodes.Assign(WorkingHelpNodes); SaveIDEWindowHelp; ModalResult:=mrOk; end; procedure TContextHelpEditorDlg.OpenDeclarationBitBtnClick(Sender: TObject); var AControl: TControl; Closer: TOpenIDEFileOnIdle; Filename: string; X: integer; Y: integer; begin AControl:=GetCurrentControl; if AControl=nil then begin IDEMessageDialog('Error','Please select a control first',mtError,[mbCancel]); exit; end; if not FindDeclarationOfIDEControl(AControl,Filename,X,Y) then begin IDEMessageDialog('Error','No declaration found for '+DbgSName(AControl),mtError,[mbCancel]); exit; end; if IDEMessageDialog('Close dialogs?','This will close all currently open modal forms and open the file '+Filename+' in the editor.', mtConfirmation,[mbOk,mbCancel])<>mrOk then exit; Closer:=TOpenIDEFileOnIdle.Create(LazarusIDE.OwningComponent); Closer.Filename:=Filename; Closer.X:=X; Closer.Y:=Y; Closer.Run; end; procedure TContextHelpEditorDlg.OpenDeclarationBitBtnShowHint(Sender: TObject; HintInfo: PHintInfo); begin HintInfo^.HintStr:='Open declaration '+GetHintForControl(GetCurrentControl); end; procedure TContextHelpEditorDlg.TestButtonClick(Sender: TObject); var AControl: TControl; begin AControl:=GetCurrentControl; if AControl=nil then exit; WorkingHelpNodes.InvokeHelp(AControl); end; procedure TContextHelpEditorDlg.SetInvoker(const AValue: TObject); var AControl: TControl; begin if FInvoker=AValue then exit; FInvoker:=AValue; //DebugLn('TContextHelpEditorDlg.SetInvoker Invoker=',dbgsName(Invoker)); if Invoker is TControl then begin AControl:=TControl(Invoker); IDEWindow:=GetParentForm(AControl); //DebugLn('TContextHelpEditorDlg.SetInvoker IDEWindow=',dbgsName(IDEWindow)); WorkingHelpNodes.FindNodeForControl(AControl,true); FillHelpNodesTreeView; SelectHelpNode(AControl); SelectControlNode(AControl); end; end; procedure TContextHelpEditorDlg.UpdateWindowControlsGroupBoxCaption; begin if IDEWindow<>nil then begin WindowControlsGroupBox.Caption:=dbgsName(IDEWindow); WindowControlsGroupBox.Enabled:=true; end else begin WindowControlsGroupBox.Caption:=lisNoIDEWindowSelected; WindowControlsGroupBox.Enabled:=false; end; end; procedure TContextHelpEditorDlg.FillControlsTreeView; procedure Add(AControl: TControl; ParentNode: TTreeNode); var i: Integer; NewNode: TTreeNode; begin NewNode:=ControlsTreeView.Items.AddChildObject(ParentNode, dbgsName(AControl),AControl); if AControl is TWinControl then begin for i:=0 to TWinControl(AControl).ControlCount-1 do Add(TWinControl(AControl).Controls[i],NewNode); end; NewNode.Expanded:=true; end; begin ControlsTreeView.BeginUpdate; ControlsTreeView.Items.Clear; if IDEWindow<>nil then Add(IDEWindow,nil); ControlsTreeView.EndUpdate; end; procedure TContextHelpEditorDlg.FillHelpNodesTreeView; procedure Add(HelpNode: TIWHelpNode; ParentNode: TTreeNode); var i: Integer; NewNode: TTreeNode; begin NewNode:=HelpNodesTreeView.Items.AddChildObject(ParentNode, HelpNode.Name,HelpNode); for i:=0 to HelpNode.Count-1 do Add(HelpNode[i],NewNode); NewNode.Expanded:=true; end; begin HelpNodesTreeView.BeginUpdate; HelpNodesTreeView.Items.Clear; Add(WorkingHelpNodes.Root,nil); HelpNodesTreeView.EndUpdate; end; procedure TContextHelpEditorDlg.UpdateHelpNodePropertiesGroupBox; var HelpNode: TIWHelpNode; begin if (csDestroying in ComponentState) then exit; HelpNode:=GetCurrentHelpNode; fLoading:=true; try if HelpNode<>nil then begin HelpNodePropertiesGroupBox.Caption:=HelpNode.Name; NodeNameEdit.Text:=HelpNode.Name; NodePathEdit.Text:=HelpNode.Path; NodeHasHelpCheckBox.Checked:=HelpNode.HasHelp; NodeIsRootCheckBox.Checked:=HelpNode.IsRoot; HelpNodePropertiesGroupBox.Enabled:=true; FullPathEdit.Text:=HelpNode.GetFullPath; end else begin HelpNodePropertiesGroupBox.Caption:=lisNoNodeSelected; HelpNodePropertiesGroupBox.Enabled:=false; end; finally fLoading:=false; end; end; procedure TContextHelpEditorDlg.SelectHelpNode(AControl: TControl); var Node: TTreeNode; begin Node:=FindHelpTreeNode(WorkingHelpNodes.FindNodeForControl(AControl)); HelpNodesTreeView.Selected:=Node; //DebugLn('TContextHelpEditorDlg.SelectHelpNode Node=',dbgs(Node),' AControl=',dbgsName(AControl),' ',dbgs(HelpNodesTreeView.Selected)); end; procedure TContextHelpEditorDlg.SelectControlNode(AControl: TControl); var Node: TTreeNode; begin Node:=FindControlTreeNode(AControl); ControlsTreeView.Selected:=Node; end; function TContextHelpEditorDlg.FindHelpTreeNode(HelpNode: TIWHelpNode): TTreeNode; function Find(HNode: TIWHelpNode): TTreeNode; var ParentTreeNode: TTreeNode; begin if HNode=nil then exit(nil); if HNode.Parent=nil then begin Result:=HelpNodesTreeView.Items.FindTopLvlNode(HNode.Name); end else begin ParentTreeNode:=Find(HNode.Parent); if ParentTreeNode=nil then Result:=nil else Result:=ParentTreeNode.FindNode(HNode.Name); end; end; begin Result:=Find(HelpNode); end; function TContextHelpEditorDlg.FindControlTreeNode(AControl: TControl): TTreeNode; function Find(TheControl: TControl): TTreeNode; var ParentTreeNode: TTreeNode; begin if TheControl=nil then exit(nil); if TheControl.Parent=nil then begin Result:=ControlsTreeView.Items.FindTopLvlNode(dbgsName(TheControl)); end else begin ParentTreeNode:=Find(TheControl.Parent); if ParentTreeNode=nil then Result:=nil else Result:=ParentTreeNode.FindNode(dbgsName(TheControl)); end; end; begin Result:=Find(AControl); end; function TContextHelpEditorDlg.GetCurrentControl: TControl; var Node: TTreeNode; begin Node:=ControlsTreeView.Selected; if Node=nil then exit(nil); Result:=TControl(Node.Data); end; function TContextHelpEditorDlg.GetCurrentHelpNode: TIWHelpNode; var Node: TTreeNode; begin Node:=HelpNodesTreeView.Selected; if Node=nil then exit(nil); Result:=TIWHelpNode(Node.Data); end; procedure TContextHelpEditorDlg.SaveHelpNodeProperties; var HelpNode: TIWHelpNode; begin if fLoading then exit; HelpNode:=GetCurrentHelpNode; if HelpNode=nil then exit; HelpNode.Name:=NodeNameEdit.Text; HelpNode.Path:=NodePathEdit.Text; HelpNode.HasHelp:=NodeHasHelpCheckBox.Checked; HelpNode.IsRoot:=NodeIsRootCheckBox.Checked; FullPathEdit.Text:=HelpNode.GetFullPath; end; function TContextHelpEditorDlg.GetHintForControl(AControl: TControl): string; var X: integer; Y: integer; Filename: string; begin if not FindDeclarationOfIDEControl(AControl,Filename,X,Y) then Result:=''; Result:=DbgSName(AControl)+': '+Filename+'('+IntToStr(Y)+','+IntToStr(X)+')'; end; procedure TContextHelpEditorDlg.SetIDEWindow(const AValue: TCustomForm); begin if FIDEWindow=AValue then exit; FIDEWindow:=AValue; UpdateWindowControlsGroupBoxCaption; FillControlsTreeView; end; { TOpenIDEFileOnIdle } procedure TOpenIDEFileOnIdle.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if Operation=opRemove then begin CloseDialogs.Remove(AComponent); CancelDialogs.Remove(AComponent); end; end; constructor TOpenIDEFileOnIdle.Create(AOwner: TComponent); begin inherited Create(AOwner); CloseDialogs:=TFPList.Create; CancelDialogs:=TFPList.Create; end; procedure TOpenIDEFileOnIdle.Run; var i: Integer; Form: TCustomForm; begin for i:=0 to Screen.CustomFormCount-1 do begin Form:=Screen.CustomForms[i]; if (CloseDialogs.IndexOf(Form)<0) and (fsModal in Form.FormState) then begin FreeNotification(Form); CloseDialogs.Add(Form); end; end; Application.AddOnIdleHandler(@OnIdle); end; destructor TOpenIDEFileOnIdle.Destroy; begin Application.RemoveOnIdleHandler(@OnIdle); inherited Destroy; FreeAndNil(CloseDialogs); FreeAndNil(CancelDialogs); end; procedure TOpenIDEFileOnIdle.OnIdle(Sender: TObject; var Done: Boolean); var i: Integer; Form: TCustomForm; begin if Ending then exit; { For example: User press ok: - cancel the context help dialog - cancel the options dialog - a question is asked } // remove pending forms that are no longer modal for i:=CloseDialogs.Count-1 downto 0 do begin Form:=TCustomForm(CloseDialogs[i]); if not (fsModal in Form.FormState) then CloseDialogs.Delete(i); end; Form:=Screen.GetCurrentModalForm; debugln(['TOpenIDEFileOnIdle.OnIdle Modal=',DbgSName(Form)]); // check if complete if CloseDialogs.Count=0 then begin debugln(['TOpenIDEFileOnIdle.OnIdle no more closing ...']); Ending:=true; if Form=nil then begin // no more modal forms open debugln(['TOpenIDEFileOnIdle.OnIdle opening ...']); LazarusIDE.DoOpenFileAndJumpToPos(Filename,Point(X,Y),-1,-1,-1,[ofDoNotLoadResource]); end; Free; end else begin // close a modal dialog if CancelDialogs.IndexOf(Form)>=0 then begin // this form was already cancelled, but is still there on idle // => user cancelled or something went wrong debugln(['TOpenIDEFileOnIdle.OnIdle closing failed']); Ending:=true; Free; end else if CloseDialogs.IndexOf(Form)>=0 then begin // close modal dialog debugln(['TOpenIDEFileOnIdle.OnIdle closing ',DbgSName(Form)]); CancelDialogs.Add(Form); CloseDialogs.Remove(Form); Form.ModalResult:=mrCancel; end; end; end; end.