1{
2 ***************************************************************************
3 *                                                                         *
4 *   This source is free software; you can redistribute it and/or modify   *
5 *   it under the terms of the GNU General Public License as published by  *
6 *   the Free Software Foundation; either version 2 of the License, or     *
7 *   (at your option) any later version.                                   *
8 *                                                                         *
9 *   This code is distributed in the hope that it will be useful, but      *
10 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
11 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
12 *   General Public License for more details.                              *
13 *                                                                         *
14 *   A copy of the GNU General Public License is available on the World    *
15 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
16 *   obtain it by writing to the Free Software Foundation,                 *
17 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
18 *                                                                         *
19 ***************************************************************************
20
21  Author: Michael Van Canneyt
22
23  Page editor. Edits 1 file. A page editor exists of a package editor
24  and an element editor, joined on a panel with a splitter between them.
25
26  It is built modular, in 3 pieces.
27  - Package editor.  Edits structure of the documentation file.
28  - Element editor.  Edits 1 element node in the documentation file.
29  - EditorPage.      Combines the above two to one visual element.
30
31  The package editor is mainly concerned with structure of the file in
32  terms of packages/topic/module/element. All things which are concerned
33  with structure are shunted to the package editor. In turn, the package
34  editor has a series of event handlers in order to react on changes.
35
36  The element editor edits 1 element. It can handle the following tags:
37  short/descr/errors/seealso/example
38
39  There is no direct interaction between the package editor and the element
40  editor. This allows changing either of them without needing to change the other.
41  The page editor handles all communication between the two.
42
43  This allows one to implement several editors. One could also implement the
44  editor page so it is split horizontal, whatever.
45
46}
47unit pgEditor;
48
49{$mode objfpc}
50{$h+}
51
52interface
53
54uses SysUtils, Classes, dom, xmlread, xmlwrite, Forms, Controls, ExtCtrls,
55     ComCtrls, Dialogs, freditor, frpeditor, fpdeutil, LazFileUtils, LazUTF8;
56
57type
58  TEditorPageNew = class(TFrame)
59
60  end;
61
62Type
63
64  { TEditorPage }
65  TEditorPage = Class(TTabSheet)
66  Private
67    FDocument : TXMLDocument;
68    FPackages : TPackageEditor;
69    FElement : TElementEditor;
70    FSplitter : TSplitter;
71    FFileNAme : String;
72    Procedure ElementSelected(Node : TDomElement) ;
73    function FindElement(const ElementName: String; Out PE,ME : TDomElement): TDomElement;
74    function FindElementNode(AParent: TDomElement; const ANodeName: String;
75      const AElementName: string): TDomElement;
76    Procedure TopicSelected(Node : TDomElement) ;
77    Procedure ModuleSelected(Node : TDomElement) ;
78    Procedure PackageSelected(Node : TDomElement) ;
79//    Procedure SelectionChanged(Sender : TObject; Node : TDomElement) ;
80//    Procedure ElementDeSelected(Sender : TObject; Node : TDomElement) ;
81    Function GetCurrentSelection : String;
82    Function GetCurrentPackage : TDomElement;
83    Function GetCurrentModule : TDomElement;
84    Function GetCurrentTopic : TDomElement;
85    Function GetCurrentElement : TDomElement;
86    Procedure SetCurrentModule(Value : TDomElement);
87    Procedure SetCurrentTopic(Value : TDomElement);
88    Procedure SetCurrentPackage(Value : TDomElement);
89    Procedure SetCurrentElement(Value : TDomElement);
90    Procedure SetModified(Value : Boolean);
91    Function  GetModified : Boolean;
92    Function MakeBackup(FN : String) : Boolean;
93    Procedure DisplayDocument(Const AStartNode : String = '');
94    Procedure ElementChanged(Sender: TObject);
95  protected
96    procedure SetParent(NewParent: TWinControl); override;
97  Public
98    constructor Create(AOwner : TComponent); override;
99    Function  FirstPackage : TDomElement;
100    Function  FirstModule(APackage : TDomElement) : TDomElement;
101    Procedure LoadFromFile(FN : String; Const AStartNode : String = '');
102    Procedure LoadFromStream(S : TStream);
103    Procedure SaveToFile(FN : String);
104    Procedure SetFileName(FN : String);
105    Procedure InsertTag(TagType : TTagType);
106    Procedure InsertLink(LinkTarget,LinkText : String);
107    Procedure InsertTable(Cols,Rows : Integer; UseHeader : Boolean);
108    Procedure InsertShortPrintLink(pLinkTarget: string);
109    Procedure InsertItemizeList(ItemsCount: Integer);
110    Procedure InsertEnumerateList(ItemsCount: Integer);
111    Procedure NewPackage(APackageName : String);
112    Procedure NewModule(AModuleName : String);
113    Procedure NewTopic(ATopicName : String);
114    procedure NewElement(AElementName: String);
115    Procedure GetElementList(List : TStrings);
116    function  GetInitialDir: String;
117    Procedure ClearDocument;
118    procedure UpdateTree;
119    Function CanInsertTag(TagType : TTagType) : Boolean;
120    Property FileName : String Read FFileName;
121    Property CurrentSelection : String Read GetCurrentSelection;
122    Property CurrentPackage : TDomElement Read GetCurrentPackage Write SetCurrentPackage;
123    Property CurrentModule : TDomElement Read GetCurrentModule Write SetCurrentModule;
124    Property CurrentTopic : TDomElement Read GetCurrentTopic Write SetCurrentTopic;
125    Property CurrentElement : TDomElement Read GetCurrentElement Write SetCurrentElement;
126    Property Modified : Boolean Read GetModified Write SetModified;
127  end;
128
129
130implementation
131
132uses lazdeopts,lazdemsg;
133
134{$R *.lfm}
135
136{ ---------------------------------------------------------------------
137  TPageEditor
138  ---------------------------------------------------------------------}
139
140constructor TEditorPage.Create(AOwner : TComponent);
141begin
142  inherited;
143  FPackages:=TPackageEditor.Create(Self);
144  FPackages.Parent:=Self;
145  FPackages.Align:=alLeft;
146  FPackages.OnSelectElement:=@ElementSelected;
147  FPackages.OnSelectModule:=@ModuleSelected;
148  FPackages.OnSelectPackage:=@PackageSelected;
149  FPackages.OnSelectTopic:=@TopicSelected;
150
151  FSplitter:=TSplitter.Create(Self);
152  FSPlitter.Parent:=Self;
153  FSplitter.Align:=alLeft;
154  FSplitter.Left:=1;
155  FSplitter.Width:=5;
156
157  FElement:=TElementEditor.Create(Self);
158  FElement.Parent:=Self;
159  FElement.Align:=AlClient;
160  FElement.OnGetElementList:=@GetELementList;
161  FElement.OnGetInitialDir:=@GetInitialDir;
162  FElement.OnChange:=@ElementChanged;
163end;
164
165
166
167Procedure TEditorPage.ClearDocument;
168
169begin
170  if (FDocument<>nil) then
171    begin
172    FDocument.Free;
173    FDocument:=Nil;
174    end;
175end;
176
177procedure TEditorPage.UpdateTree;
178begin
179  FPackages.UpdateTree;
180end;
181
182function TEditorPage.CanInsertTag(TagType: TTagType): Boolean;
183begin
184  Result:=FElement.CanInsertTag(TagType);
185end;
186
187Procedure TEditorPage.LoadFromFile(FN : String; Const AStartNode : String = '');
188
189Var
190  F : TFileStream;
191
192begin
193  ClearDocument;
194  F:=TFileStream.Create(UTF8ToSys(FN),fmOpenRead);
195  Try
196    SetFileName(FN);
197    ReadXMLFile(FDocument,F);
198    DisplayDocument(AStartNode);
199    FPackages.ExpandTree;
200  finally
201    F.Free;
202  end;
203end;
204
205Procedure TEditorPage.LoadFromStream(S : TStream);
206
207begin
208  ClearDocument;
209  ReadXMLFile(FDocument,S);
210  SetFileName(SNewDocument);
211  DisplayDocument;
212end;
213
214Procedure TEditorPage.SetFileName(FN : String);
215
216begin
217  FFileName:=FN;
218  Caption:=ChangeFileExt(ExtractFileName(FN),'');
219end;
220
221Function TEditorPage.MakeBackup(FN : String) : Boolean;
222
223Var
224  BN : String;
225
226begin
227  Result:=Not CreateBackup;
228  If not Result then
229    begin
230    BN:=ChangeFileExt(FN,BackupExtension);
231    Result:=RenameFileUTF8(FN,BN);
232    end;
233end;
234
235Procedure TEditorPage.SaveToFile(FN : String);
236begin
237  MakeBackup(FN);
238  if FN <> FFileName then SetFileName(FN);
239  If FElement.Modified then FElement.Save;
240  WriteXMLFile(FDocument, FN);
241  Modified :=False;
242  FElement.Refresh;
243end;
244
245function TEditorPage.FindElementNode(AParent : TDomElement; Const ANodeName : String; Const AElementName : string) : TDomElement;
246
247Var
248  N : TDomNode;
249
250begin
251  Result:=Nil;
252  N:=AParent.FirstChild;
253  While (Result=Nil) and (N<>Nil) do
254    begin
255    if (N.NodeType=ELEMENT_NODE) and (N.NodeName=ANodeName) then
256       If (AElementName='') or (CompareText((N as TDomElement).AttribStrings['name'],AElementName)=0) then
257         Result:=N as TDomElement;
258    N:=N.NextSibling;
259    end;
260end;
261
262
263function TEditorPage.FindElement(Const ElementName : String; Out PE,ME : TDomElement) : TDomElement;
264
265  Function GetNextPart(Var N : String) : String;
266
267  Var
268    p : integer;
269
270  begin
271    P:=Pos('.',N);
272    if P=0 then
273      P:=Length(N)+1;
274    Result:=Copy(N,1,P-1);
275    Delete(N,1,P);
276  end;
277
278Var
279  PN,N : String;
280
281begin
282  Result:=Nil;
283  PE:=Nil;
284  ME:=Nil;
285  N:=ElementName;
286  // Extract package name.
287  PN:=GetNextPart(N);
288  // Search package node
289  PE:=FindElementNode(FDocument.DocumentElement,'package',PN);
290  // if not found, assume first part is modulename in first package.
291  if (PE=Nil) then
292    begin
293    PE:=FindElementNode(FDocument.DocumentElement,'package','');
294    N:=ElementName;
295    end;
296  if (PE=Nil) then // No package node !
297    exit;
298  // Extract Module name
299  PN:=GetNextPart(N);
300  ME:=FindElementNode(PE,'module',PN);
301  // if not found, assume elementname is element in first module.
302  if (ME=Nil) then
303    begin
304    ME:=FindElementNode(PE,'module','');
305    N:=ElementName;
306    end;
307  if (ME=Nil) then // No module node !
308    exit;
309  Result:=FindElementNode(ME,'element',N);
310end;
311
312Procedure TEditorPage.DisplayDocument(Const AStartNode : String = '');
313
314Var
315  PE,ME,EE : TDomElement;
316
317begin
318  EE:=Nil;
319  if (AStartNode <> '') then
320    begin
321    EE:=FindElement(AStartNode,PE,ME);
322    If (EE=Nil) then
323      ShowMessage(Format(SStartNodeNotFound,[AStartNode]));
324    end;
325  FPackages.DescriptionNode:=FDocument.DocumentElement;
326  if (EE<>Nil) then
327    begin
328    FPackages.CurrentPackage:=PE;
329    FPackages.CurrentModule:=ME;
330    FPackages.CurrentElement:=EE;
331    end;
332end;
333
334procedure TEditorPage.ElementChanged(Sender: TObject);
335begin
336  if Sender=nil then ;
337  TPackageEditor(FPackages).UpdateSelectedNodeStatus;
338end;
339
340procedure TEditorPage.SetParent(NewParent: TWinControl);
341begin
342  inherited SetParent(NewParent);
343  if Assigned(NewParent) then
344    FSplitter.Width:=5;
345end;
346
347
348
349Procedure TEditorPage.ElementSelected(Node : TDomElement) ;
350
351Var
352  OldNode : TDomElement;
353
354begin
355  OldNode:=FElement.Element;
356  If OldNode<>Node then
357    FElement.Element:=Node;
358end;
359
360Procedure TEditorPage.PackageSelected(Node : TDomElement) ;
361
362begin
363  ElementSelected(Node);
364end;
365
366Procedure TEditorPage.ModuleSelected(Node : TDomElement) ;
367
368begin
369  ElementSelected(Node);
370end;
371
372Procedure TEditorPage.TopicSelected(Node : TDomElement) ;
373
374begin
375  ElementSelected(Node);
376end;
377
378
379Procedure TEditorPage.InsertTag(TagType : TTagType);
380
381begin
382  FElement.InsertTag(TagType)
383end;
384
385Procedure TEditorPage.InsertLink(LinkTarget,LinkText : String);
386
387begin
388  FElement.InsertLink(LinkTarget,LinkText);
389end;
390
391
392Procedure TEditorPage.InsertTable(Cols,Rows : Integer; UseHeader : Boolean);
393
394begin
395  Felement.InsertTable(Cols,Rows,UseHeader);
396end;
397
398
399procedure TEditorPage.InsertShortPrintLink(pLinkTarget: string);
400begin
401  FElement.InsertPrintShortLink(pLinkTarget);
402end;
403
404procedure TEditorPage.InsertItemizeList(ItemsCount: Integer);
405begin
406  Felement.InsertItemizeList(ItemsCount);
407end;
408
409procedure TEditorPage.InsertEnumerateList(ItemsCount: Integer);
410begin
411  Felement.InsertEnumerateList(ItemsCount);
412end;
413
414
415Function TEditorPage.GetCurrentSelection : String;
416
417begin
418  Result:=FElement.CurrentSelection;
419end;
420
421Procedure TEditorPage.NewPackage(APackageName : String);
422
423Var
424  P : TDomElement;
425
426begin
427  P:=FDocument.CreateElement('package');
428  P['name']:=APAckageName;
429  FDocument.DocumentElement.AppendChild(P);
430  FPackages.Refresh;
431  FPackages.Modified:=True;
432  CurrentPackage:=P;
433end;
434
435Function TEditorPage.FirstPackage : TDomElement;
436
437Var
438  N : TDomNode;
439
440begin
441  N:=FDocument.DocumentElement.FirstChild;
442  While (N<>Nil) and Not IsPackageNode(N) do
443    N:=N.NextSibling;
444  Result:=TDomElement(N);
445end;
446
447Function TEditorPage.FirstModule(APackage : TDomElement) : TDomElement;
448
449Var
450  N : TDomNode;
451
452begin
453  N:=APAckage.FirstChild;
454  While (N<>Nil) and Not IsModuleNode(N) do
455      N:=N.NextSibling;
456  Result:=TDomElement(N);
457end;
458
459Procedure TEditorPage.NewModule(AModuleName : String);
460
461Var
462  M,P : TDomElement;
463
464begin
465  If CurrentPackage<>Nil then
466    P:=CurrentPackage
467  else
468    P:=FirstPackage;
469  If (P=Nil) then
470    Raise Exception.CreateFmt(SErrNoPackageForModule,[AModuleName]);
471  M:=FDocument.CreateElement('module');
472  M['name']:=AModuleName;
473  P.AppendChild(M);
474  FPackages.Refresh;
475  FPackages.Modified:=True;
476  CurrentModule:=M;
477end;
478
479Procedure TEditorPage.NewTopic(ATopicName : String);
480
481Var
482  T,M,P : TDomElement;
483
484begin
485  {
486    If currently a topic is selected, make a subtopic, or a sibling topic.
487    If no topic is selected, then make a topic under the current module or
488    package. A menu to move topics up/down is needed...
489  }
490  if (CurrentTopic<>Nil) then
491    begin
492    M:=CurrentTopic.ParentNode as TDomElement;
493    If (M.NodeName='module') or (M.NodeName='topic') then
494      P:=M
495    else
496      P:=CurrentTopic;
497    end
498  else if (CurrentModule<>Nil) then
499    P:=CurrentModule
500  else if (CurrentPackage<>Nil) then
501    P:=CurrentPackage
502  else
503    P:=FirstPackage;
504  If (P=Nil) then
505    Raise Exception.CreateFmt(SErrNoNodeForTopic,[ATopicName]);
506  T:=FDocument.CreateElement('topic');
507  T['name']:=ATopicName;
508  P.AppendChild(T);
509  FPackages.Refresh;
510  FPackages.Modified:=True;
511  CurrentTopic:=T;
512end;
513
514Procedure TEditorPage.NewElement(AElementName : String);
515
516Var
517  P,E,M : TDomElement;
518
519begin
520  If CurrentModule<>Nil then
521    M:=CurrentModule
522  else
523    begin
524    P:=FirstPackage;
525    If P<>Nil then
526      M:=FirstModule(P)
527    else
528      M:=Nil;
529    If M<>Nil then
530      CurrentModule:=M;
531    end;
532  If (M=Nil) then
533    Raise Exception.CreateFmt(SErrNoModuleForElement,[AElementName]);
534  E:=FDocument.CreateElement('element');
535  E['name']:=AElementName;
536  M.AppendChild(E);
537  FPackages.AddElement(E);
538end;
539
540Function TEditorPage.GetCurrentPackage : TDomElement;
541
542begin
543  Result:=FPackages.CurrentPackage;
544end;
545
546
547Function TEditorPage.GetCurrentModule : TDomElement;
548
549begin
550  Result:=FPackages.CurrentModule;
551end;
552
553
554Function TEditorPage.GetCurrentTopic : TDomElement;
555
556begin
557  Result:=FPackages.CurrentTopic;
558end;
559
560
561Function TEditorPage.GetCurrentElement : TDomElement;
562begin
563  Result:=FElement.Element;
564end;
565
566Procedure TEditorPage.SetCurrentElement(Value : TDomElement);
567
568begin
569  FPackages.CurrentElement:=Value;
570end;
571
572
573Procedure TEditorPage.SetCurrentModule(Value : TDomElement);
574
575begin
576  FPackages.CurrentModule:=Value;
577end;
578
579
580Procedure TEditorPage.SetCurrentTopic(Value : TDomElement);
581
582begin
583  FPackages.CurrentTopic:=Value;
584end;
585
586
587Procedure TEditorPage.SetCurrentPackage(Value : TDomElement);
588
589begin
590  FPackages.CurrentPackage:=Value;
591end;
592
593Procedure TEditorPage.SetModified(Value : Boolean);
594
595begin
596  If Not Value then
597    begin
598    FPackages.Modified:=False;
599    FElement.Modified:=False;
600    FElement.SavedNode:=False;
601    end;
602end;
603
604Function TEditorPage.GetModified : Boolean;
605
606begin
607  Result:=FPackages.Modified or
608          FElement.Modified or
609          FElement.SavedNode;
610end;
611
612Procedure TEditorPage.GetElementList(List : TStrings);
613
614Var
615  N : TDOmNode;
616
617begin
618 With List do
619   begin
620   Clear;
621   If Assigned(CurrentModule) then
622     begin
623     N:=Currentmodule.FirstChild;
624     While (N<>Nil) do
625       begin
626       If (N is TDomElement) and (N.NodeName='element') then
627         Add(TDomElement(N)['name']);
628       N:=N.NextSibling;
629       end;
630     end;
631   end;
632end;
633
634function TEditorPage.GetInitialDir: String;
635begin
636  result := '';
637  if FileExistsUTF8(FFileName) then
638    Result := ExtractFilePath(FFileName);
639end;
640
641end.
642