lazarus/doceditor/pgeditor.pp
2017-01-29 21:04:32 +00:00

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.