mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 00:48:05 +02:00
642 lines
16 KiB
ObjectPascal
642 lines
16 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: Michael Van Canneyt
|
|
|
|
Page editor. Edits 1 file. A page editor exists of a package editor
|
|
and an element editor, joined on a panel with a splitter between them.
|
|
|
|
It is built modular, in 3 pieces.
|
|
- Package editor. Edits structure of the documentation file.
|
|
- Element editor. Edits 1 element node in the documentation file.
|
|
- EditorPage. Combines the above two to one visual element.
|
|
|
|
The package editor is mainly concerned with structure of the file in
|
|
terms of packages/topic/module/element. All things which are concerned
|
|
with structure are shunted to the package editor. In turn, the package
|
|
editor has a series of event handlers in order to react on changes.
|
|
|
|
The element editor edits 1 element. It can handle the following tags:
|
|
short/descr/errors/seealso/example
|
|
|
|
There is no direct interaction between the package editor and the element
|
|
editor. This allows changing either of them without needing to change the other.
|
|
The page editor handles all communication between the two.
|
|
|
|
This allows one to implement several editors. One could also implement the
|
|
editor page so it is split horizontal, whatever.
|
|
|
|
}
|
|
unit pgEditor;
|
|
|
|
{$mode objfpc}
|
|
{$h+}
|
|
|
|
interface
|
|
|
|
uses SysUtils, Classes, dom, xmlread, xmlwrite, Forms, Controls, ExtCtrls,
|
|
ComCtrls, Dialogs, freditor, frpeditor, fpdeutil, LazFileUtils, LazUTF8;
|
|
|
|
type
|
|
TEditorPageNew = class(TFrame)
|
|
|
|
end;
|
|
|
|
Type
|
|
|
|
{ TEditorPage }
|
|
TEditorPage = Class(TTabSheet)
|
|
Private
|
|
FDocument : TXMLDocument;
|
|
FPackages : TPackageEditor;
|
|
FElement : TElementEditor;
|
|
FSplitter : TSplitter;
|
|
FFileNAme : String;
|
|
Procedure ElementSelected(Node : TDomElement) ;
|
|
function FindElement(const ElementName: String; Out PE,ME : TDomElement): TDomElement;
|
|
function FindElementNode(AParent: TDomElement; const ANodeName: String;
|
|
const AElementName: string): TDomElement;
|
|
Procedure TopicSelected(Node : TDomElement) ;
|
|
Procedure ModuleSelected(Node : TDomElement) ;
|
|
Procedure PackageSelected(Node : TDomElement) ;
|
|
// Procedure SelectionChanged(Sender : TObject; Node : TDomElement) ;
|
|
// Procedure ElementDeSelected(Sender : TObject; Node : TDomElement) ;
|
|
Function GetCurrentSelection : String;
|
|
Function GetCurrentPackage : TDomElement;
|
|
Function GetCurrentModule : TDomElement;
|
|
Function GetCurrentTopic : TDomElement;
|
|
Function GetCurrentElement : TDomElement;
|
|
Procedure SetCurrentModule(Value : TDomElement);
|
|
Procedure SetCurrentTopic(Value : TDomElement);
|
|
Procedure SetCurrentPackage(Value : TDomElement);
|
|
Procedure SetCurrentElement(Value : TDomElement);
|
|
Procedure SetModified(Value : Boolean);
|
|
Function GetModified : Boolean;
|
|
Function MakeBackup(FN : String) : Boolean;
|
|
Procedure DisplayDocument(Const AStartNode : String = '');
|
|
Procedure ElementChanged(Sender: TObject);
|
|
protected
|
|
procedure SetParent(NewParent: TWinControl); override;
|
|
Public
|
|
constructor Create(AOwner : TComponent); override;
|
|
Function FirstPackage : TDomElement;
|
|
Function FirstModule(APackage : TDomElement) : TDomElement;
|
|
Procedure LoadFromFile(FN : String; Const AStartNode : String = '');
|
|
Procedure LoadFromStream(S : TStream);
|
|
Procedure SaveToFile(FN : String);
|
|
Procedure SetFileName(FN : String);
|
|
Procedure InsertTag(TagType : TTagType);
|
|
Procedure InsertLink(LinkTarget,LinkText : String);
|
|
Procedure InsertTable(Cols,Rows : Integer; UseHeader : Boolean);
|
|
Procedure InsertShortPrintLink(pLinkTarget: string);
|
|
Procedure InsertItemizeList(ItemsCount: Integer);
|
|
Procedure InsertEnumerateList(ItemsCount: Integer);
|
|
Procedure NewPackage(APackageName : String);
|
|
Procedure NewModule(AModuleName : String);
|
|
Procedure NewTopic(ATopicName : String);
|
|
procedure NewElement(AElementName: String);
|
|
Procedure GetElementList(List : TStrings);
|
|
function GetInitialDir: String;
|
|
Procedure ClearDocument;
|
|
procedure UpdateTree;
|
|
Function CanInsertTag(TagType : TTagType) : Boolean;
|
|
Property FileName : String Read FFileName;
|
|
Property CurrentSelection : String Read GetCurrentSelection;
|
|
Property CurrentPackage : TDomElement Read GetCurrentPackage Write SetCurrentPackage;
|
|
Property CurrentModule : TDomElement Read GetCurrentModule Write SetCurrentModule;
|
|
Property CurrentTopic : TDomElement Read GetCurrentTopic Write SetCurrentTopic;
|
|
Property CurrentElement : TDomElement Read GetCurrentElement Write SetCurrentElement;
|
|
Property Modified : Boolean Read GetModified Write SetModified;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
uses lazdeopts,lazdemsg;
|
|
|
|
{$R *.lfm}
|
|
|
|
{ ---------------------------------------------------------------------
|
|
TPageEditor
|
|
---------------------------------------------------------------------}
|
|
|
|
constructor TEditorPage.Create(AOwner : TComponent);
|
|
begin
|
|
inherited;
|
|
FPackages:=TPackageEditor.Create(Self);
|
|
FPackages.Parent:=Self;
|
|
FPackages.Align:=alLeft;
|
|
FPackages.OnSelectElement:=@ElementSelected;
|
|
FPackages.OnSelectModule:=@ModuleSelected;
|
|
FPackages.OnSelectPackage:=@PackageSelected;
|
|
FPackages.OnSelectTopic:=@TopicSelected;
|
|
|
|
FSplitter:=TSplitter.Create(Self);
|
|
FSPlitter.Parent:=Self;
|
|
FSplitter.Align:=alLeft;
|
|
FSplitter.Left:=1;
|
|
FSplitter.Width:=5;
|
|
|
|
FElement:=TElementEditor.Create(Self);
|
|
FElement.Parent:=Self;
|
|
FElement.Align:=AlClient;
|
|
FElement.OnGetElementList:=@GetELementList;
|
|
FElement.OnGetInitialDir:=@GetInitialDir;
|
|
FElement.OnChange:=@ElementChanged;
|
|
end;
|
|
|
|
|
|
|
|
Procedure TEditorPage.ClearDocument;
|
|
|
|
begin
|
|
if (FDocument<>nil) then
|
|
begin
|
|
FDocument.Free;
|
|
FDocument:=Nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TEditorPage.UpdateTree;
|
|
begin
|
|
FPackages.UpdateTree;
|
|
end;
|
|
|
|
function TEditorPage.CanInsertTag(TagType: TTagType): Boolean;
|
|
begin
|
|
Result:=FElement.CanInsertTag(TagType);
|
|
end;
|
|
|
|
Procedure TEditorPage.LoadFromFile(FN : String; Const AStartNode : String = '');
|
|
|
|
Var
|
|
F : TFileStream;
|
|
|
|
begin
|
|
ClearDocument;
|
|
F:=TFileStream.Create(UTF8ToSys(FN),fmOpenRead);
|
|
Try
|
|
SetFileName(FN);
|
|
ReadXMLFile(FDocument,F);
|
|
DisplayDocument(AStartNode);
|
|
FPackages.ExpandTree;
|
|
finally
|
|
F.Free;
|
|
end;
|
|
end;
|
|
|
|
Procedure TEditorPage.LoadFromStream(S : TStream);
|
|
|
|
begin
|
|
ClearDocument;
|
|
ReadXMLFile(FDocument,S);
|
|
SetFileName(SNewDocument);
|
|
DisplayDocument;
|
|
end;
|
|
|
|
Procedure TEditorPage.SetFileName(FN : String);
|
|
|
|
begin
|
|
FFileName:=FN;
|
|
Caption:=ChangeFileExt(ExtractFileName(FN),'');
|
|
end;
|
|
|
|
Function TEditorPage.MakeBackup(FN : String) : Boolean;
|
|
|
|
Var
|
|
BN : String;
|
|
|
|
begin
|
|
Result:=Not CreateBackup;
|
|
If not Result then
|
|
begin
|
|
BN:=ChangeFileExt(FN,BackupExtension);
|
|
Result:=RenameFileUTF8(FN,BN);
|
|
end;
|
|
end;
|
|
|
|
Procedure TEditorPage.SaveToFile(FN : String);
|
|
begin
|
|
MakeBackup(FN);
|
|
if FN <> FFileName then SetFileName(FN);
|
|
If FElement.Modified then FElement.Save;
|
|
WriteXMLFile(FDocument, FN);
|
|
Modified :=False;
|
|
FElement.Refresh;
|
|
end;
|
|
|
|
function TEditorPage.FindElementNode(AParent : TDomElement; Const ANodeName : String; Const AElementName : string) : TDomElement;
|
|
|
|
Var
|
|
N : TDomNode;
|
|
|
|
begin
|
|
Result:=Nil;
|
|
N:=AParent.FirstChild;
|
|
While (Result=Nil) and (N<>Nil) do
|
|
begin
|
|
if (N.NodeType=ELEMENT_NODE) and (N.NodeName=ANodeName) then
|
|
If (AElementName='') or (CompareText((N as TDomElement).AttribStrings['name'],AElementName)=0) then
|
|
Result:=N as TDomElement;
|
|
N:=N.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TEditorPage.FindElement(Const ElementName : String; Out PE,ME : TDomElement) : TDomElement;
|
|
|
|
Function GetNextPart(Var N : String) : String;
|
|
|
|
Var
|
|
p : integer;
|
|
|
|
begin
|
|
P:=Pos('.',N);
|
|
if P=0 then
|
|
P:=Length(N)+1;
|
|
Result:=Copy(N,1,P-1);
|
|
Delete(N,1,P);
|
|
end;
|
|
|
|
Var
|
|
PN,N : String;
|
|
|
|
begin
|
|
Result:=Nil;
|
|
PE:=Nil;
|
|
ME:=Nil;
|
|
N:=ElementName;
|
|
// Extract package name.
|
|
PN:=GetNextPart(N);
|
|
// Search package node
|
|
PE:=FindElementNode(FDocument.DocumentElement,'package',PN);
|
|
// if not found, assume first part is modulename in first package.
|
|
if (PE=Nil) then
|
|
begin
|
|
PE:=FindElementNode(FDocument.DocumentElement,'package','');
|
|
N:=ElementName;
|
|
end;
|
|
if (PE=Nil) then // No package node !
|
|
exit;
|
|
// Extract Module name
|
|
PN:=GetNextPart(N);
|
|
ME:=FindElementNode(PE,'module',PN);
|
|
// if not found, assume elementname is element in first module.
|
|
if (ME=Nil) then
|
|
begin
|
|
ME:=FindElementNode(PE,'module','');
|
|
N:=ElementName;
|
|
end;
|
|
if (ME=Nil) then // No module node !
|
|
exit;
|
|
Result:=FindElementNode(ME,'element',N);
|
|
end;
|
|
|
|
Procedure TEditorPage.DisplayDocument(Const AStartNode : String = '');
|
|
|
|
Var
|
|
PE,ME,EE : TDomElement;
|
|
|
|
begin
|
|
EE:=Nil;
|
|
if (AStartNode <> '') then
|
|
begin
|
|
EE:=FindElement(AStartNode,PE,ME);
|
|
If (EE=Nil) then
|
|
ShowMessage(Format(SStartNodeNotFound,[AStartNode]));
|
|
end;
|
|
FPackages.DescriptionNode:=FDocument.DocumentElement;
|
|
if (EE<>Nil) then
|
|
begin
|
|
FPackages.CurrentPackage:=PE;
|
|
FPackages.CurrentModule:=ME;
|
|
FPackages.CurrentElement:=EE;
|
|
end;
|
|
end;
|
|
|
|
procedure TEditorPage.ElementChanged(Sender: TObject);
|
|
begin
|
|
if Sender=nil then ;
|
|
TPackageEditor(FPackages).UpdateSelectedNodeStatus;
|
|
end;
|
|
|
|
procedure TEditorPage.SetParent(NewParent: TWinControl);
|
|
begin
|
|
inherited SetParent(NewParent);
|
|
if Assigned(NewParent) then
|
|
FSplitter.Width:=5;
|
|
end;
|
|
|
|
|
|
|
|
Procedure TEditorPage.ElementSelected(Node : TDomElement) ;
|
|
|
|
Var
|
|
OldNode : TDomElement;
|
|
|
|
begin
|
|
OldNode:=FElement.Element;
|
|
If OldNode<>Node then
|
|
FElement.Element:=Node;
|
|
end;
|
|
|
|
Procedure TEditorPage.PackageSelected(Node : TDomElement) ;
|
|
|
|
begin
|
|
ElementSelected(Node);
|
|
end;
|
|
|
|
Procedure TEditorPage.ModuleSelected(Node : TDomElement) ;
|
|
|
|
begin
|
|
ElementSelected(Node);
|
|
end;
|
|
|
|
Procedure TEditorPage.TopicSelected(Node : TDomElement) ;
|
|
|
|
begin
|
|
ElementSelected(Node);
|
|
end;
|
|
|
|
|
|
Procedure TEditorPage.InsertTag(TagType : TTagType);
|
|
|
|
begin
|
|
FElement.InsertTag(TagType)
|
|
end;
|
|
|
|
Procedure TEditorPage.InsertLink(LinkTarget,LinkText : String);
|
|
|
|
begin
|
|
FElement.InsertLink(LinkTarget,LinkText);
|
|
end;
|
|
|
|
|
|
Procedure TEditorPage.InsertTable(Cols,Rows : Integer; UseHeader : Boolean);
|
|
|
|
begin
|
|
Felement.InsertTable(Cols,Rows,UseHeader);
|
|
end;
|
|
|
|
|
|
procedure TEditorPage.InsertShortPrintLink(pLinkTarget: string);
|
|
begin
|
|
FElement.InsertPrintShortLink(pLinkTarget);
|
|
end;
|
|
|
|
procedure TEditorPage.InsertItemizeList(ItemsCount: Integer);
|
|
begin
|
|
Felement.InsertItemizeList(ItemsCount);
|
|
end;
|
|
|
|
procedure TEditorPage.InsertEnumerateList(ItemsCount: Integer);
|
|
begin
|
|
Felement.InsertEnumerateList(ItemsCount);
|
|
end;
|
|
|
|
|
|
Function TEditorPage.GetCurrentSelection : String;
|
|
|
|
begin
|
|
Result:=FElement.CurrentSelection;
|
|
end;
|
|
|
|
Procedure TEditorPage.NewPackage(APackageName : String);
|
|
|
|
Var
|
|
P : TDomElement;
|
|
|
|
begin
|
|
P:=FDocument.CreateElement('package');
|
|
P['name']:=APAckageName;
|
|
FDocument.DocumentElement.AppendChild(P);
|
|
FPackages.Refresh;
|
|
FPackages.Modified:=True;
|
|
CurrentPackage:=P;
|
|
end;
|
|
|
|
Function TEditorPage.FirstPackage : TDomElement;
|
|
|
|
Var
|
|
N : TDomNode;
|
|
|
|
begin
|
|
N:=FDocument.DocumentElement.FirstChild;
|
|
While (N<>Nil) and Not IsPackageNode(N) do
|
|
N:=N.NextSibling;
|
|
Result:=TDomElement(N);
|
|
end;
|
|
|
|
Function TEditorPage.FirstModule(APackage : TDomElement) : TDomElement;
|
|
|
|
Var
|
|
N : TDomNode;
|
|
|
|
begin
|
|
N:=APAckage.FirstChild;
|
|
While (N<>Nil) and Not IsModuleNode(N) do
|
|
N:=N.NextSibling;
|
|
Result:=TDomElement(N);
|
|
end;
|
|
|
|
Procedure TEditorPage.NewModule(AModuleName : String);
|
|
|
|
Var
|
|
M,P : TDomElement;
|
|
|
|
begin
|
|
If CurrentPackage<>Nil then
|
|
P:=CurrentPackage
|
|
else
|
|
P:=FirstPackage;
|
|
If (P=Nil) then
|
|
Raise Exception.CreateFmt(SErrNoPackageForModule,[AModuleName]);
|
|
M:=FDocument.CreateElement('module');
|
|
M['name']:=AModuleName;
|
|
P.AppendChild(M);
|
|
FPackages.Refresh;
|
|
FPackages.Modified:=True;
|
|
CurrentModule:=M;
|
|
end;
|
|
|
|
Procedure TEditorPage.NewTopic(ATopicName : String);
|
|
|
|
Var
|
|
T,M,P : TDomElement;
|
|
|
|
begin
|
|
{
|
|
If currently a topic is selected, make a subtopic, or a sibling topic.
|
|
If no topic is selected, then make a topic under the current module or
|
|
package. A menu to move topics up/down is needed...
|
|
}
|
|
if (CurrentTopic<>Nil) then
|
|
begin
|
|
M:=CurrentTopic.ParentNode as TDomElement;
|
|
If (M.NodeName='module') or (M.NodeName='topic') then
|
|
P:=M
|
|
else
|
|
P:=CurrentTopic;
|
|
end
|
|
else if (CurrentModule<>Nil) then
|
|
P:=CurrentModule
|
|
else if (CurrentPackage<>Nil) then
|
|
P:=CurrentPackage
|
|
else
|
|
P:=FirstPackage;
|
|
If (P=Nil) then
|
|
Raise Exception.CreateFmt(SErrNoNodeForTopic,[ATopicName]);
|
|
T:=FDocument.CreateElement('topic');
|
|
T['name']:=ATopicName;
|
|
P.AppendChild(T);
|
|
FPackages.Refresh;
|
|
FPackages.Modified:=True;
|
|
CurrentTopic:=T;
|
|
end;
|
|
|
|
Procedure TEditorPage.NewElement(AElementName : String);
|
|
|
|
Var
|
|
P,E,M : TDomElement;
|
|
|
|
begin
|
|
If CurrentModule<>Nil then
|
|
M:=CurrentModule
|
|
else
|
|
begin
|
|
P:=FirstPackage;
|
|
If P<>Nil then
|
|
M:=FirstModule(P)
|
|
else
|
|
M:=Nil;
|
|
If M<>Nil then
|
|
CurrentModule:=M;
|
|
end;
|
|
If (M=Nil) then
|
|
Raise Exception.CreateFmt(SErrNoModuleForElement,[AElementName]);
|
|
E:=FDocument.CreateElement('element');
|
|
E['name']:=AElementName;
|
|
M.AppendChild(E);
|
|
FPackages.AddElement(E);
|
|
end;
|
|
|
|
Function TEditorPage.GetCurrentPackage : TDomElement;
|
|
|
|
begin
|
|
Result:=FPackages.CurrentPackage;
|
|
end;
|
|
|
|
|
|
Function TEditorPage.GetCurrentModule : TDomElement;
|
|
|
|
begin
|
|
Result:=FPackages.CurrentModule;
|
|
end;
|
|
|
|
|
|
Function TEditorPage.GetCurrentTopic : TDomElement;
|
|
|
|
begin
|
|
Result:=FPackages.CurrentTopic;
|
|
end;
|
|
|
|
|
|
Function TEditorPage.GetCurrentElement : TDomElement;
|
|
begin
|
|
Result:=FElement.Element;
|
|
end;
|
|
|
|
Procedure TEditorPage.SetCurrentElement(Value : TDomElement);
|
|
|
|
begin
|
|
FPackages.CurrentElement:=Value;
|
|
end;
|
|
|
|
|
|
Procedure TEditorPage.SetCurrentModule(Value : TDomElement);
|
|
|
|
begin
|
|
FPackages.CurrentModule:=Value;
|
|
end;
|
|
|
|
|
|
Procedure TEditorPage.SetCurrentTopic(Value : TDomElement);
|
|
|
|
begin
|
|
FPackages.CurrentTopic:=Value;
|
|
end;
|
|
|
|
|
|
Procedure TEditorPage.SetCurrentPackage(Value : TDomElement);
|
|
|
|
begin
|
|
FPackages.CurrentPackage:=Value;
|
|
end;
|
|
|
|
Procedure TEditorPage.SetModified(Value : Boolean);
|
|
|
|
begin
|
|
If Not Value then
|
|
begin
|
|
FPackages.Modified:=False;
|
|
FElement.Modified:=False;
|
|
FElement.SavedNode:=False;
|
|
end;
|
|
end;
|
|
|
|
Function TEditorPage.GetModified : Boolean;
|
|
|
|
begin
|
|
Result:=FPackages.Modified or
|
|
FElement.Modified or
|
|
FElement.SavedNode;
|
|
end;
|
|
|
|
Procedure TEditorPage.GetElementList(List : TStrings);
|
|
|
|
Var
|
|
N : TDOmNode;
|
|
|
|
begin
|
|
With List do
|
|
begin
|
|
Clear;
|
|
If Assigned(CurrentModule) then
|
|
begin
|
|
N:=Currentmodule.FirstChild;
|
|
While (N<>Nil) do
|
|
begin
|
|
If (N is TDomElement) and (N.NodeName='element') then
|
|
Add(TDomElement(N)['name']);
|
|
N:=N.NextSibling;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TEditorPage.GetInitialDir: String;
|
|
begin
|
|
result := '';
|
|
if FileExistsUTF8(FFileName) then
|
|
Result := ExtractFilePath(FFileName);
|
|
end;
|
|
|
|
end.
|