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   see for todo list: http://wiki.lazarus.freepascal.org/index.php/LazDoc
22 }
23 
24 unit FPDocEditWindow;
25 
26 {$mode objfpc}{$H+}
27 
28 {off $define VerboseCodeHelp}
29 
30 interface
31 
32 uses
33   // FCL
34   Classes, SysUtils,
35   // LazUtils
36   Laz2_DOM, Laz2_XMLRead, LazStringUtils, LazTracer,
37   // LCL
38   LResources, StdCtrls, Buttons, ComCtrls, Controls, Dialogs,
39   ExtCtrls, Forms, Graphics, LCLType, LCLProc,
40   // Synedit
41   SynEdit, SynHighlighterXML, SynEditFoldedView, SynEditWrappedView,
42   // codetools
43   FileProcs, CodeCache, CodeToolManager, CTXMLFixFragment,
44   // IDEIntf
45   IDEWindowIntf, ProjectIntf, LazIDEIntf, IDEHelpIntf, Menus,
46   SrcEditorIntf, IDEDialogs, LazFileUtils, IDEImagesIntf,
47   // IDE
48   IDEOptionDefs, EnvironmentOpts, PackageSystem, LazarusIDEStrConsts,
49   FPDocSelectInherited, FPDocSelectLink, CodeHelp;
50 
51 type
52   TFPDocEditorFlag = (
53     fpdefReading,
54     fpdefWriting,
55     fpdefCodeCacheNeedsUpdate,
56     fpdefChainNeedsUpdate,
57     fpdefCaptionNeedsUpdate,
58     fpdefValueControlsNeedsUpdate,
59     fpdefInheritedControlsNeedsUpdate,
60     fpdefTopicSettingUp,
61     fpdefTopicNeedsUpdate,
62     fpdefWasHidden
63     );
64   TFPDocEditorFlags = set of TFPDocEditorFlag;
65 
66   { TFPDocEditor }
67 
68   TFPDocEditor = class(TForm)
69     AddLinkToInheritedButton: TButton;
70     BoldFormatButton: TSpeedButton;
71     BrowseExampleButton: TButton;
72     OpenXMLButton: TButton;
73     ShortPanel: TPanel;
74     DescrShortEdit: TEdit;
75     SynXMLSyn1: TSynXMLSyn;
76     TopicShort: TEdit;
77     TopicDescrSynEdit: TSynEdit;
78     Panel3: TPanel;
79     TopicListBox: TListBox;
80     NewTopicNameEdit: TEdit;
81     NewTopicButton: TButton;
82     CopyFromInheritedButton: TButton;
83     CreateButton: TButton;
84     DescrSynEdit: TSynEdit;
85     DescrTabSheet: TTabSheet;
86     ErrorsSynEdit: TSynEdit;
87     ErrorsTabSheet: TTabSheet;
88     ExampleEdit: TEdit;
89     ExampleTabSheet: TTabSheet;
90     InheritedShortEdit: TEdit;
91     InheritedShortLabel: TLabel;
92     InheritedTabSheet: TTabSheet;
93     InsertCodeTagButton: TSpeedButton;
94     InsertLinkSpeedButton: TSpeedButton;
95     InsertParagraphSpeedButton: TSpeedButton;
96     InsertRemarkButton: TSpeedButton;
97     InsertVarTagButton: TSpeedButton;
98     ItalicFormatButton: TSpeedButton;
99     LeftBtnPanel: TPanel;
100     LinkEdit: TEdit;
101     LinkLabel: TLabel;
102     Panel1: TPanel;
103     Panel2: TPanel;
104     SaveButton: TSpeedButton;
105     SeeAlsoSynEdit: TSynEdit;
106     MoveToInheritedButton: TButton;
107     OpenDialog: TOpenDialog;
108     PageControl: TPageControl;
109     SeeAlsoTabSheet: TTabSheet;
110     ShortEdit: TEdit;
111     ShortLabel: TLabel;
112     ShortTabSheet: TTabSheet;
113     InsertPrintShortSpeedButton: TSpeedButton;
114     InsertURLTagSpeedButton: TSpeedButton;
115     TopicSheet: TTabSheet;
116     UnderlineFormatButton: TSpeedButton;
117     procedure AddLinkToInheritedButtonClick(Sender: TObject);
118     procedure ApplicationIdle(Sender: TObject; var Done: Boolean);
119     procedure BrowseExampleButtonClick(Sender: TObject);
120     procedure CopyFromInheritedButtonClick(Sender: TObject);
121     procedure CopyShortToDescrMenuItemClick(Sender: TObject);
122     procedure CreateButtonClick(Sender: TObject);
123     procedure DescrSynEditChange(Sender: TObject);
124     procedure ErrorsSynEditChange(Sender: TObject);
125     procedure ExampleEditChange(Sender: TObject);
126     procedure FormatButtonClick(Sender: TObject);
127     procedure FormCreate(Sender: TObject);
128     procedure FormDestroy(Sender: TObject);
129     procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
130     procedure FormShow(Sender: TObject);
131     procedure InsertLinkSpeedButtonClick(Sender: TObject);
132     procedure LinkEditChange(Sender: TObject);
133     procedure MoveToInheritedButtonClick(Sender: TObject);
134     procedure NewTopicButtonClick(Sender: TObject);
135     procedure OpenXMLButtonClick(Sender: TObject);
136     procedure PageControlChange(Sender: TObject);
137     procedure SaveButtonClick(Sender: TObject);
138     procedure SeeAlsoSynEditChange(Sender: TObject);
139     procedure ShortEditChange(Sender: TObject);
140     procedure TopicControlEnter(Sender: TObject);
141     procedure TopicDescrSynEditChange(Sender: TObject);
142     procedure TopicListBoxClick(Sender: TObject);
143   private
144     FCaretXY: TPoint;
145     FModified: Boolean;
146     FFlags: TFPDocEditorFlags;
147     fUpdateLock: Integer;
148     fSourceFilename: string;
149     fDocFile: TLazFPDocFile;
150     fChain: TCodeHelpElementChain;
151     FOldValues: TFPDocElementValues;
152     FOldVisualValues: TFPDocElementValues;
GetDocnull153     function GetDoc: TXMLdocument;
GetDocFilenull154     function GetDocFile: TLazFPDocFile;
GetSourceFilenamenull155     function GetSourceFilename: string;
GetFirstElementnull156     function GetFirstElement: TDOMNode;
157 
GetContextTitlenull158     function GetContextTitle(Element: TCodeHelpElement): string;
159 
FindInheritedIndexnull160     function FindInheritedIndex: integer;
161     procedure Save(CheckGUI: boolean = false);
GetGUIValuesnull162     function GetGUIValues: TFPDocElementValues;
163     procedure SetModified(const AValue: boolean);
WriteNodenull164     function WriteNode(Element: TCodeHelpElement; Values: TFPDocElementValues;
165                        Interactive: Boolean): Boolean;
166     procedure UpdateCodeCache;
167     procedure UpdateChain;
168     procedure UpdateCaption;
169     procedure UpdateValueControls;
170     procedure UpdateInheritedControls;
171     procedure OnFPDocChanging(Sender: TObject; FPDocFPFile: TLazFPDocFile);
172     procedure OnFPDocChanged(Sender: TObject; FPDocFPFile: TLazFPDocFile);
173     procedure LoadGUIValues(Element: TCodeHelpElement);
174     procedure MoveToInherited(Element: TCodeHelpElement);
GetDefaultDocFilenull175     function GetDefaultDocFile(CreateIfNotExists: Boolean = False): TLazFPDocFile;
ExtractIDFromLinkTagnull176     function ExtractIDFromLinkTag(const LinkTag: string; out ID, Title: string): boolean;
CreateElementnull177     function CreateElement(Element: TCodeHelpElement): Boolean;
178     procedure UpdateButtons;
GetCurrentUnitNamenull179     function GetCurrentUnitName: string;
GetCurrentOwnerNamenull180     function GetCurrentOwnerName: string;
181     procedure JumpToError(Item : TFPDocItem; LineCol: TPoint);
182     procedure OpenXML;
GUIModifiednull183     function GUIModified: boolean;
184     procedure DoEditorUpdate(Sender: TObject);
185     procedure DoEditorMouseUp(Sender: TObject);
186   private
187     FFollowCursor: boolean;
188     FIdleConnected: boolean;
189     FLastTopicControl: TControl;
190     FCurrentTopic: String;
191     procedure SetFollowCursor(AValue: boolean);
192     procedure SetIdleConnected(AValue: boolean);
193     procedure UpdateTopicCombo;
194     procedure ClearTopicControls;
195     procedure UpdateTopic;
196   protected
197     procedure UpdateShowing; override;
198     procedure Loaded; override;
199   public
200     procedure Reset;
201     procedure InvalidateChain;
202     procedure LoadIdentifierAt(const SrcFilename: string; const Caret: TPoint);
203     procedure LoadIdentifierAtCursor;
204     procedure BeginUpdate;
205     procedure EndUpdate;
206     procedure ClearEntry(DoSave: Boolean);
207     property DocFile: TLazFPDocFile read GetDocFile;
208     property Doc: TXMLdocument read GetDoc;
209     property SourceFilename: string read GetSourceFilename;
210     property CaretXY: TPoint read FCaretXY;
211     property Modified: boolean read FModified write SetModified;
212     property IdleConnected: boolean read FIdleConnected write SetIdleConnected;
213     property FollowCursor: boolean read FFollowCursor write SetFollowCursor;
214   end;
215 
216 var
217   FPDocEditor: TFPDocEditor = nil;
218 
219 procedure DoShowFPDocEditor(State: TIWGetFormState = iwgfShowOnTop);
220 
221 implementation
222 
223 {$R *.lfm}
224 {$R lazdoc.res}
225 
226 { TFPDocEditor }
227 
228 procedure DoShowFPDocEditor(State: TIWGetFormState);
229 begin
230   if FPDocEditor = Nil then
231     IDEWindowCreators.CreateForm(FPDocEditor,TFPDocEditor,
232        State=iwgfDisabled,LazarusIDE.OwningComponent)
233   else if State=iwgfDisabled then
234     FPDocEditor.DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('DoShowFPDocEditor'){$ENDIF};
235 
236   if State>=iwgfShow then
237     IDEWindowCreators.ShowForm(FPDocEditor,State=iwgfShowOnTop);
238 end;
239 
GetFirstElementnull240 function TFPDocEditor.GetFirstElement: TDOMNode;
241 var
242   CurDocFile: TLazFPDocFile;
243 begin
244   Result:=nil;
245   CurDocFile:=DocFile;
246   if CurDocFile=nil then exit;
247   Result:=CurDocFile.GetFirstElement;
248 end;
249 
250 procedure TFPDocEditor.FormCreate(Sender: TObject);
251   procedure UpdateSynEdit(ASynEd: TSynEdit);
252   var
253     fld: TSynEditFoldedView;
254   begin
255     fld := TSynEditFoldedView(ASynEd.TextViewsManager.SynTextViewByClass[TSynEditFoldedView]);
256     if fld <> nil then
257       fld.FoldProvider.Enabled := False;
258     TLazSynEditLineWrapPlugin.Create(ASynEd);
259   end;
260 begin
261   Caption := lisCodeHelpMainFormCaption;
262 
263   ShortTabSheet.Caption := lisCodeHelpShortTag;
264   InheritedTabSheet.Caption := lisCodeHelpInherited;
265   DescrTabSheet.Caption := lisCodeHelpDescrTag;
266   ErrorsTabSheet.Caption := lisCodeHelpErrorsTag;
267   SeeAlsoTabSheet.Caption := lisCodeHelpSeeAlsoTag;
268   ExampleTabSheet.Caption := lisCodeHelpExampleTag;
269 
270   PageControl.PageIndex := 0;
271 
272   BoldFormatButton.Hint := lisCodeHelpHintBoldFormat;
273   ItalicFormatButton.Hint := lisCodeHelpHintItalicFormat;
274   UnderlineFormatButton.Hint := lisCodeHelpHintUnderlineFormat;
275   InsertCodeTagButton.Hint := lisCodeHelpHintInsertCodeTag;
276   InsertRemarkButton.Hint := lisCodeHelpHintRemarkTag;
277   InsertVarTagButton.Hint := lisCodeHelpHintVarTag;
278   InsertParagraphSpeedButton.Hint := lisCodeHelpInsertParagraphFormattingTag;
279   InsertLinkSpeedButton.Hint := lisCodeHelpInsertALink;
280   InsertPrintShortSpeedButton.Hint:=lisInsertPrintshortTag2;
281   InsertURLTagSpeedButton.Hint:=lisInsertUrlTag;
282 
283   ShortLabel.Caption:=lisShort;
284   LinkLabel.Caption:=lisLink;
285   CreateButton.Caption := lisCodeHelpCreateButton;
286   OpenXMLButton.Caption:=lisOpenXML;
287   OpenXMLButton.Enabled:=false;
288   SaveButton.Caption := '';
289   SaveButton.Enabled:=false;
290   SaveButton.Hint:=lisSave;
291   SaveButton.ShowHint:=true;
292 
293   BrowseExampleButton.Caption := lisCodeHelpBrowseExampleButton;
294 
295   MoveToInheritedButton.Caption:=lisLDMoveEntriesToInherited;
296   CopyFromInheritedButton.Caption:=lisLDCopyFromInherited;
297   AddLinkToInheritedButton.Caption:=lisLDAddLinkToInherited;
298 
299   Reset;
300 
301   CodeHelpBoss.AddHandlerOnChanging(@OnFPDocChanging);
302   CodeHelpBoss.AddHandlerOnChanged(@OnFPDocChanged);
303 
304   Name := NonModalIDEWindowNames[nmiwFPDocEditor];
305 
306   IDEImages.AssignImage(BoldFormatButton, 'formatbold');
307   IDEImages.AssignImage(UnderlineFormatButton, 'formatunderline');
308   IDEImages.AssignImage(ItalicFormatButton, 'formatitalic');
309   IDEImages.AssignImage(InsertVarTagButton, 'insertvartag');
310   IDEImages.AssignImage(InsertCodeTagButton, 'insertcodetag');
311   IDEImages.AssignImage(InsertRemarkButton, 'insertremark');
312   IDEImages.AssignImage(InsertURLTagSpeedButton, 'formatunderline');
313   IDEImages.AssignImage(SaveButton, 'laz_save');
314 
315   SourceEditorManagerIntf.RegisterChangeEvent(semEditorActivate, @DoEditorUpdate);
316   SourceEditorManagerIntf.RegisterChangeEvent(semEditorStatus, @DoEditorUpdate);
317   SourceEditorManagerIntf.RegisterChangeEvent(semEditorMouseUp, @DoEditorMouseUp);
318 
319   UpdateSynEdit(TopicDescrSynEdit);
320   UpdateSynEdit(DescrSynEdit);
321   UpdateSynEdit(ErrorsSynEdit);
322   UpdateSynEdit(SeeAlsoSynEdit);
323 
324   FollowCursor:=true;
325   IdleConnected:=true;
326 end;
327 
328 procedure TFPDocEditor.FormDestroy(Sender: TObject);
329 begin
330   IdleConnected:=false;
331   Reset;
332   FreeAndNil(fChain);
333   if assigned(CodeHelpBoss) then
334     CodeHelpBoss.RemoveAllHandlersOfObject(Self);
335   Application.RemoveAllHandlersOfObject(Self);
336   if SourceEditorManagerIntf<>nil then begin
337     SourceEditorManagerIntf.UnRegisterChangeEvent(semEditorActivate, @DoEditorUpdate);
338     SourceEditorManagerIntf.UnRegisterChangeEvent(semEditorStatus, @DoEditorUpdate);
339   end;
340 end;
341 
342 procedure TFPDocEditor.FormKeyDown(Sender: TObject; var Key: Word;
343   Shift: TShiftState);
344 begin
345   if (Key=VK_S) and (Shift=[ssCtrl]) then begin
346     Save(true);
347     Key:=VK_UNKNOWN;
348   end;
349 end;
350 
351 procedure TFPDocEditor.FormShow(Sender: TObject);
352 begin
353   DoEditorUpdate(nil);
354 end;
355 
356 procedure TFPDocEditor.FormatButtonClick(Sender: TObject);
357 
358   procedure InsertTag(const StartTag, EndTag: String);
359   begin
360     if PageControl.ActivePage = ShortTabSheet then begin
361       ShortEdit.SelText := StartTag + ShortEdit.SelText + EndTag;
362       DescrShortEdit.Text:=ShortEdit.Text;
363     end else if PageControl.ActivePage = DescrTabSheet then
364       DescrSynEdit.SelText := StartTag + DescrSynEdit.SelText + EndTag
365     else if PageControl.ActivePage = ErrorsTabSheet then
366       ErrorsSynEdit.SelText := StartTag + ErrorsSynEdit.SelText + EndTag
367     else if PageControl.ActivePage = TopicSheet then begin
368       if (FLastTopicControl = TopicShort) then
369         TopicShort.SelText := StartTag + TopicShort.SelText + EndTag;
370       if (FLastTopicControl = TopicDescrSynEdit) then
371         TopicDescrSynEdit.SelText := StartTag + TopicDescrSynEdit.SelText + EndTag;
372     end
373     else
374       exit;
375     Modified:=true;
376   end;
377 
378 begin
379   case TSpeedButton(Sender).Tag of
380     //bold
381     0:
382       InsertTag('<b>', '</b>');
383     //italic
384     1:
385       InsertTag('<i>', '</i>');
386     //underline
387     2:
388       InsertTag('<u>', '</u>');
389     //code tag
390     3:
391       InsertTag('<p><code>', '</code></p>');
392     //remark tag
393     4:
394       InsertTag('<p><remark>', '</remark></p>');
395     //var tag
396     5:
397       InsertTag('<var>', '</var>');
398     //paragraph tag
399     6:
400       InsertTag('<p>', '</p>');
401     //printshort
402     7:
403       if (fChain<>nil) and (fChain.Count>0) then
404         InsertTag('<printshort id="'+fChain[0].ElementName+'"/>','');
405     //url tag
406     8:
407       InsertTag('<url href="">', '</url>');
408   end;
409 end;
410 
411 procedure TFPDocEditor.InsertLinkSpeedButtonClick(Sender: TObject);
412 var
413   Link: string;
414   LinkTitle: string;
415   LinkSrc: String;
416 begin
417   if ShowFPDocLinkEditorDialog(fSourceFilename,DocFile,Link,LinkTitle)<>mrOk then exit;
418   if Link='' then exit;
419   LinkSrc:='<link id="'+Link+'"';
420   if LinkTitle='' then begin
421     LinkSrc:=LinkSrc+'/>';
422   end else begin
423     LinkSrc:=LinkSrc+'>'+LinkTitle+'</link>';
424   end;
425   if PageControl.ActivePage = ShortTabSheet then begin
426     ShortEdit.SelText := LinkSrc;
427     DescrShortEdit.Text := ShortEdit.Text;
428   end;
429   if PageControl.ActivePage = DescrTabSheet then
430     DescrSynEdit.SelText := LinkSrc;
431   if PageControl.ActivePage = SeeAlsoTabSheet then
432     SeeAlsoSynEdit.SelText := LinkSrc;
433   if PageControl.ActivePage = ErrorsTabSheet then
434     ErrorsSynEdit.SelText := LinkSrc;
435   if PageControl.ActivePage = TopicSheet then begin
436     if (FLastTopicControl = TopicShort) then
437       TopicShort.SelText := LinkSrc;
438     if (FLastTopicControl = TopicDescrSynEdit) then
439       TopicDescrSynEdit.SelText := LinkSrc;
440   end;
441 
442   Modified:=true;
443 end;
444 
445 procedure TFPDocEditor.LinkEditChange(Sender: TObject);
446 begin
447   if fpdefReading in FFlags then exit;
448   if LinkEdit.Text<>FOldVisualValues[fpdiElementLink] then
449     Modified:=true;
450 end;
451 
452 procedure TFPDocEditor.ApplicationIdle(Sender: TObject; var Done: Boolean);
453 var
454   ActiveForm: TCustomForm;
455 begin
456   if (fUpdateLock>0) then
457   begin
458     DebugLn(['WARNING: TFPDocEditor.ApplicationIdle fUpdateLock>0']);
459     exit;
460   end;
461   if not IsVisible then begin
462     Include(FFlags,fpdefWasHidden);
463     IdleConnected:=false;
464     exit;
465   end;
466   ActiveForm:=Screen.ActiveCustomForm;
467   if (ActiveForm<>nil) and (fsModal in ActiveForm.FormState) then exit;
468   Done:=false;
469   if fpdefCodeCacheNeedsUpdate in FFlags then
470     UpdateCodeCache
471   else if fpdefChainNeedsUpdate in FFlags then
472     UpdateChain
473   else if fpdefCaptionNeedsUpdate in FFlags then
474     UpdateCaption
475   else if fpdefValueControlsNeedsUpdate in FFlags then
476     UpdateValueControls
477   else if fpdefInheritedControlsNeedsUpdate in FFlags then
478     UpdateInheritedControls
479   else if fpdefTopicNeedsUpdate in FFlags then
480     UpdateTopicCombo
481   else begin
482     //debugln(['TFPDocEditor.ApplicationIdle updated']);
483     Done:=true;
484     IdleConnected:=false;
485   end;
486 end;
487 
488 procedure TFPDocEditor.MoveToInheritedButtonClick(Sender: TObject);
489 var
490   i: Integer;
491   Element: TCodeHelpElement;
492   Candidates: TFPList;
493   FPDocSelectInheritedDlg: TFPDocSelectInheritedDlg;
494   ShortDescr: String;
495 begin
496   if fChain=nil then exit;
497   Candidates:=nil;
498   FPDocSelectInheritedDlg:=nil;
499   try
500     // find all entries till the first inherited entry with a description
501     for i:=1 to fChain.Count-1 do begin
502       Element:=fChain[i];
503       if Candidates=nil then
504         Candidates:=TFPList.Create;
505       Candidates.Add(Element);
506       if (Element.ElementNode<>nil)
507       and (Element.FPDocFile.GetValueFromNode(Element.ElementNode,fpdiShort)<>'')
508       then
509         break;
510     end;
511 
512     // choose one entry
513     if (Candidates=nil) or (Candidates.Count=0) then exit;
514     if Candidates.Count=1 then begin
515       // there is only one candidate
516       Element:=TCodeHelpElement(Candidates[0]);
517       if (Element.ElementNode<>nil) then begin
518         ShortDescr:=Element.FPDocFile.GetValueFromNode(Element.ElementNode,fpdiShort);
519         if ShortDescr<>'' then begin
520           // the inherited entry already contains a description.
521           // ask if it should be really replaced
522           if IDEQuestionDialog(lisCodeHelpConfirmreplace,
523             GetContextTitle(Element)+' already contains the help:'+LineEnding+ShortDescr,
524             mtConfirmation, [mrYes, lisReplace,
525                              mrCancel]) <> mrYes then exit;
526         end;
527       end;
528     end else begin
529       // there is more than one candidate
530       // => ask which one to replace
531       FPDocSelectInheritedDlg:=TFPDocSelectInheritedDlg.Create(nil);
532       FPDocSelectInheritedDlg.InheritedComboBox.Items.Clear;
533       for i:=0 to Candidates.Count-1 do begin
534         Element:=TCodeHelpElement(Candidates[i]);
535         FPDocSelectInheritedDlg.InheritedComboBox.Items.Add(
536                                                       GetContextTitle(Element));
537       end;
538       if FPDocSelectInheritedDlg.ShowModal<>mrOk then exit;
539       i:=FPDocSelectInheritedDlg.InheritedComboBox.ItemIndex;
540       if i<0 then exit;
541       Element:=TCodeHelpElement(Candidates[i]);
542     end;
543 
544     // move the content of the current entry to the inherited entry
545     MoveToInherited(Element);
546   finally
547     FPDocSelectInheritedDlg.Free;
548     Candidates.Free;
549   end;
550 end;
551 
552 procedure TFPDocEditor.NewTopicButtonClick(Sender: TObject);
553 var
554   Dfile: TLazFPDocFile;
555 begin
556   if NewTopicNameEdit.Text = '' then exit;
557   Dfile := GetDefaultDocFile(True);
558   if not assigned(DFile) then exit;
559   if DFile.GetModuleTopic(NewTopicNameEdit.Text) = nil then begin
560     DFile.CreateModuleTopic(NewTopicNameEdit.Text);
561     CodeHelpBoss.SaveFPDocFile(DFile);
562   end;
563   UpdateTopicCombo;
564   TopicListBox.ItemIndex := TopicListBox.Items.IndexOf(NewTopicNameEdit.Text);
565   TopicListBoxClick(Sender);
566 end;
567 
568 procedure TFPDocEditor.OpenXMLButtonClick(Sender: TObject);
569 begin
570   OpenXML;
571 end;
572 
573 procedure TFPDocEditor.PageControlChange(Sender: TObject);
574 begin
575   UpdateButtons;
576 end;
577 
578 procedure TFPDocEditor.SaveButtonClick(Sender: TObject);
579 begin
580   Save;
581   UpdateValueControls;
582 end;
583 
584 procedure TFPDocEditor.SeeAlsoSynEditChange(Sender: TObject);
585 begin
586   if fpdefReading in FFlags then exit;
587   if SeeAlsoSynEdit.Text<>FOldVisualValues[fpdiSeeAlso] then
588     Modified:=true;
589 end;
590 
591 procedure TFPDocEditor.ShortEditChange(Sender: TObject);
592 // called by ShortEdit and DescrShortEdit
593 var
594   NewShort: String;
595 begin
596   if fpdefReading in FFlags then exit;
597   //debugln(['TFPDocEditor.ShortEditChange ',DbgSName(Sender)]);
598   if Sender=DescrShortEdit then
599     NewShort:=DescrShortEdit.Text
600   else
601     NewShort:=ShortEdit.Text;
602   if NewShort<>FOldVisualValues[fpdiShort] then
603     Modified:=true;
604   // copy to the other edit
605   if Sender=DescrShortEdit then
606     ShortEdit.Text:=NewShort
607   else
608     DescrShortEdit.Text:=NewShort;
609 end;
610 
611 procedure TFPDocEditor.TopicControlEnter(Sender: TObject);
612 begin
613   FLastTopicControl := TControl(Sender);
614 end;
615 
616 procedure TFPDocEditor.TopicDescrSynEditChange(Sender: TObject);
617 begin
618   if fpdefReading in FFlags then exit;
619   if fpdefTopicSettingUp in FFlags then exit;
620   Modified := True;
621 end;
622 
623 procedure TFPDocEditor.TopicListBoxClick(Sender: TObject);
624 begin
625   if fpdefTopicSettingUp in FFlags then exit;
626   if (FCurrentTopic <> '') and Modified then
627     Save;
628   UpdateTopic;
629 end;
630 
GetContextTitlenull631 function TFPDocEditor.GetContextTitle(Element: TCodeHelpElement): string;
632 // get codetools path. for example: TButton.Align
633 begin
634   Result:='';
635   if Element=nil then exit;
636   Result:=Element.ElementName;
637 end;
638 
TFPDocEditor.GetDocnull639 function TFPDocEditor.GetDoc: TXMLdocument;
640 begin
641   if DocFile<>nil then
642     Result:=DocFile.Doc
643   else
644     Result:=nil;
645 end;
646 
647 procedure TFPDocEditor.ClearTopicControls;
648 var
649   OldSettingUp: boolean;
650 begin
651   OldSettingUp:=fpdefTopicSettingUp in FFlags;
652   Include(FFlags, fpdefTopicSettingUp);
653   try
654     TopicShort.Clear;
655     TopicDescrSynEdit.Clear;
656     TopicShort.Enabled := False;
657     TopicDescrSynEdit.Enabled := False;
658   finally
659     if not OldSettingUp then
660       Exclude(FFlags, fpdefTopicSettingUp);
661   end;
662 end;
663 
GetDocFilenull664 function TFPDocEditor.GetDocFile: TLazFPDocFile;
665 begin
666   Result:=nil;
667   if fChain<>nil then
668     Result:=fChain.DocFile
669   else
670     Result:=fDocFile;
671 end;
672 
GetSourceFilenamenull673 function TFPDocEditor.GetSourceFilename: string;
674 begin
675   Result:=fSourceFilename;
676 end;
677 
678 procedure TFPDocEditor.UpdateCaption;
679 var
680   strCaption: String;
681   Filename: String;
682 begin
683   if fUpdateLock>0 then begin
684     Include(FFlags,fpdefCaptionNeedsUpdate);
685     exit;
686   end;
687   Exclude(FFlags,fpdefCaptionNeedsUpdate);
688 
689   {$IFDEF VerboseCodeHelp}
690   DebugLn(['TFPDocEditForm.UpdateCaption START']);
691   {$ENDIF}
692   strCaption := lisCodeHelpMainFormCaption + ' - ';
693 
694   if (fChain <> nil) and (fChain.Count>0) then
695     strCaption := strCaption + GetContextTitle(fChain[0]) + ' - '
696   else
697     strCaption := strCaption + lisCodeHelpNoTagCaption + ' - ';
698 
699   if DocFile<>nil then begin
700     Filename:=DocFile.Filename;
701     if (LazarusIDE.ActiveProject<>nil) then
702       Filename:=LazarusIDE.ActiveProject.GetShortFilename(Filename,true);
703     Caption := strCaption + Filename;
704   end else
705     Caption := strCaption + lisCodeHelpNoTagCaption;
706   {$IFDEF VerboseCodeHelp}
707   DebugLn(['TFPDocEditor.UpdateCaption ',Caption]);
708   {$ENDIF}
709 end;
710 
711 procedure TFPDocEditor.UpdateValueControls;
712 var
713   Element: TCodeHelpElement;
714 begin
715   if fUpdateLock>0 then begin
716     Include(FFlags,fpdefValueControlsNeedsUpdate);
717     exit;
718   end;
719   Exclude(FFlags,fpdefValueControlsNeedsUpdate);
720 
721   {$IFDEF VerboseCodeHelp}
722   DebugLn(['TFPDocEditForm.UpdateValueControls START']);
723   {$ENDIF}
724   Element:=nil;
725   if (fChain<>nil) and (fChain.Count>0) then
726     Element:=fChain[0];
727   LoadGUIValues(Element);
728   SaveButton.Enabled:=FModified;
729 end;
730 
731 procedure TFPDocEditor.UpdateInheritedControls;
732 var
733   i: LongInt;
734   Element: TCodeHelpElement;
735   ShortDescr: String;
736 begin
737   if fUpdateLock>0 then begin
738     Include(FFlags,fpdefInheritedControlsNeedsUpdate);
739     exit;
740   end;
741   Exclude(FFlags,fpdefInheritedControlsNeedsUpdate);
742 
743   {$IFDEF VerboseCodeHelp}
744   DebugLn(['TFPDocEditForm.UpdateInheritedControls START']);
745   {$ENDIF}
746   i:=FindInheritedIndex;
747   if i<0 then begin
748     InheritedShortEdit.Text:='';
749     InheritedShortEdit.Enabled:=false;
750     InheritedShortLabel.Caption:=lisCodeHelpnoinheriteddescriptionfound;
751   end else begin
752     Element:=fChain[i];
753     ShortDescr:=Element.FPDocFile.GetValueFromNode(Element.ElementNode,fpdiShort);
754     InheritedShortEdit.Text:=ShortDescr;
755     InheritedShortEdit.Enabled:=true;
756     InheritedShortLabel.Caption:=lisCodeHelpShortdescriptionOf+' '
757                                  +GetContextTitle(Element);
758   end;
759   MoveToInheritedButton.Enabled:=(fChain<>nil)
760                                  and (fChain.Count>1)
761                                  and (ShortEdit.Text<>'');
762   CopyFromInheritedButton.Enabled:=(i>=0);
763   AddLinkToInheritedButton.Enabled:=(i>=0);
764 end;
765 
766 procedure TFPDocEditor.UpdateChain;
767 var
768   Code: TCodeBuffer;
769   LDResult: TCodeHelpParseResult;
770   NewChain: TCodeHelpElementChain;
771   CacheWasUsed: Boolean;
772 begin
773   fDocFile:=nil;
774   FreeAndNil(fChain);
775   if fUpdateLock>0 then begin
776     Include(FFlags,fpdefChainNeedsUpdate);
777     exit;
778   end;
779   Exclude(FFlags,fpdefChainNeedsUpdate);
780 
781   if (fSourceFilename='') or (CaretXY.X<1) or (CaretXY.Y<1) then exit;
782 
783   {$IFDEF VerboseCodeHelp}
784   DebugLn(['TFPDocEditForm.UpdateChain START ',fSourceFilename,' ',dbgs(CaretXY)]);
785   {$ENDIF}
786   NewChain:=nil;
787   try
788     // fetch pascal source
789     Code:=CodeToolBoss.LoadFile(fSourceFilename,true,false);
790     if Code=nil then begin
791       DebugLn(['TFPDocEditForm.UpdateChain failed loading ',fSourceFilename]);
792       exit;
793     end;
794 
795     // start getting the fpdoc element chain
796     LDResult:=CodeHelpBoss.GetElementChain(Code,CaretXY.X,CaretXY.Y,true,
797                                            NewChain,CacheWasUsed);
798     case LDResult of
799     chprParsing:
800       begin
801         Include(FFlags,fpdefChainNeedsUpdate);
802         DebugLn(['TFPDocEditForm.UpdateChain ToDo: still parsing CodeHelpBoss.GetElementChain for ',fSourceFilename,' ',dbgs(CaretXY)]);
803         exit;
804       end;
805     chprFailed:
806       begin
807         {$IFDEF VerboseFPDocFails}
808         DebugLn(['TFPDocEditForm.UpdateChain failed CodeHelpBoss.GetElementChain for ',fSourceFilename,' ',dbgs(CaretXY)]);
809         {$ENDIF}
810         exit;
811       end;
812     else
813       {$IFDEF VerboseCodeHelp}
814       NewChain.WriteDebugReport;
815       {$ENDIF}
816       fChain:=NewChain;
817       fDocFile:=fChain.DocFile;
818       NewChain:=nil;
819     end;
820   finally
821     NewChain.Free;
822   end;
823   if (fDocFile=nil) then begin
824     // load default docfile, needed to show syntax errors in xml and for topics
825     fDocFile:=GetDefaultDocFile;
826   end;
827   OpenXMLButton.Enabled:=fDocFile<>nil;
828   if fDocFile<>nil then
829     OpenXMLButton.Hint:=fDocFile.Filename
830   else
831     OpenXMLButton.Hint:='';
832 end;
833 
834 procedure TFPDocEditor.OnFPDocChanging(Sender: TObject;
835   FPDocFPFile: TLazFPDocFile);
836 begin
837   if fpdefWriting in FFlags then exit;
838   if (fChain<>nil) and (fChain.IndexOfFile(FPDocFPFile)>=0) then
839     InvalidateChain
840   else if (fDocFile<>nil) and (fDocFile=FPDocFPFile) then
841     Include(FFlags,fpdefTopicNeedsUpdate);
842 end;
843 
844 procedure TFPDocEditor.OnFPDocChanged(Sender: TObject;
845   FPDocFPFile: TLazFPDocFile);
846 begin
847   if fpdefWriting in FFlags then exit;
848   if FPDocFPFile=nil then exit;
849   // maybe eventually update the editor
850 end;
851 
852 procedure TFPDocEditor.LoadGUIValues(Element: TCodeHelpElement);
853 var
854   EnabledState: Boolean;
855   OldModified: Boolean;
856 begin
857   if fpdefReading in FFlags then exit;
858   OldModified:=FModified;
859 
860   Include(FFlags,fpdefReading);
861   try
862     EnabledState := (Element<>nil) and (Element.ElementNode<>nil);
863 
864     //CreateButton.Enabled := (Element<>nil) and (Element.ElementNode=nil)
865     //                        and (Element.ElementName<>'');
866 
867     if EnabledState then
868     begin
869       FOldValues:=Element.FPDocFile.GetValuesFromNode(Element.ElementNode);
870       FOldVisualValues[fpdiShort]:=ReplaceLineEndings(FOldValues[fpdiShort],'');
871       FOldVisualValues[fpdiElementLink]:=LineBreaksToSystemLineBreaks(FOldValues[fpdiElementLink]);
872       FOldVisualValues[fpdiDescription]:=LineBreaksToSystemLineBreaks(FOldValues[fpdiDescription]);
873       FOldVisualValues[fpdiErrors]:=LineBreaksToSystemLineBreaks(FOldValues[fpdiErrors]);
874       FOldVisualValues[fpdiSeeAlso]:=LineBreaksToSystemLineBreaks(FOldValues[fpdiSeeAlso]);
875       FOldVisualValues[fpdiExample]:=LineBreaksToSystemLineBreaks(FOldValues[fpdiExample]);
876       //DebugLn(['TFPDocEditor.LoadGUIValues Short="',dbgstr(FOldValues[fpdiShort]),'"']);
877     end
878     else
879     begin
880       FOldVisualValues[fpdiShort]:='';
881       FOldVisualValues[fpdiElementLink]:='';
882       FOldVisualValues[fpdiDescription]:='';
883       FOldVisualValues[fpdiErrors]:='';
884       FOldVisualValues[fpdiSeeAlso]:='';
885       FOldVisualValues[fpdiExample]:='';
886     end;
887     ShortEdit.Text := FOldVisualValues[fpdiShort];
888     DescrShortEdit.Text := ShortEdit.Text;
889     //debugln(['TFPDocEditor.LoadGUIValues "',ShortEdit.Text,'" "',FOldVisualValues[fpdiShort],'"']);
890     LinkEdit.Text := FOldVisualValues[fpdiElementLink];
891     DescrSynEdit.Lines.Text := FOldVisualValues[fpdiDescription];
892     //debugln(['TFPDocEditor.LoadGUIValues DescrMemo="',dbgstr(DescrSynEdit.Lines.Text),'" Descr="',dbgstr(FOldVisualValues[fpdiDescription]),'"']);
893     SeeAlsoSynEdit.Text := FOldVisualValues[fpdiSeeAlso];
894     ErrorsSynEdit.Lines.Text := FOldVisualValues[fpdiErrors];
895     ExampleEdit.Text := FOldVisualValues[fpdiExample];
896 
897     ShortEdit.Enabled := EnabledState;
898     DescrShortEdit.Enabled := ShortEdit.Enabled;
899     LinkEdit.Enabled := EnabledState;
900     DescrSynEdit.Enabled := EnabledState;
901     SeeAlsoSynEdit.Enabled := EnabledState;
902     ErrorsSynEdit.Enabled := EnabledState;
903     ExampleEdit.Enabled := EnabledState;
904     BrowseExampleButton.Enabled := EnabledState;
905 
906     FModified:=OldModified;
907     SaveButton.Enabled:=false;
908 
909   finally
910     Exclude(FFlags,fpdefReading);
911   end;
912 end;
913 
914 procedure TFPDocEditor.MoveToInherited(Element: TCodeHelpElement);
915 var
916   Values: TFPDocElementValues;
917 begin
918   Values:=GetGUIValues;
919   WriteNode(Element,Values,true);
920 end;
921 
TFPDocEditor.ExtractIDFromLinkTagnull922 function TFPDocEditor.ExtractIDFromLinkTag(const LinkTag: string; out ID, Title: string
923   ): boolean;
924 // extract id and title from example:
925 // <link id="TCustomControl"/>
926 // <link id="#lcl.Graphics.TCanvas">TCanvas</link>
927 var
928   StartPos: Integer;
929   EndPos: LongInt;
930 begin
931   Result:=false;
932   ID:='';
933   Title:='';
934   StartPos:=length('<link id="')+1;
935   if copy(LinkTag,1,StartPos-1)<>'<link id="' then
936     exit;
937   EndPos:=StartPos;
938   while (EndPos<=length(LinkTag)) do begin
939     if LinkTag[EndPos]='"' then begin
940       ID:=copy(LinkTag,StartPos,EndPos-StartPos);
941       Title:='';
942       Result:=true;
943       // extract title
944       StartPos:=EndPos;
945       while (StartPos<=length(LinkTag)) and (LinkTag[StartPos]<>'>') do inc(StartPos);
946       if LinkTag[StartPos-1]='\' then begin
947         // no title
948       end else begin
949         // has title
950         inc(StartPos);
951         EndPos:=StartPos;
952         while (EndPos<=length(LinkTag)) and (LinkTag[EndPos]<>'<') do inc(EndPos);
953         Title:=copy(LinkTag,StartPos,EndPos-StartPos);
954       end;
955       exit;
956     end;
957     inc(EndPos);
958   end;
959 end;
960 
CreateElementnull961 function TFPDocEditor.CreateElement(Element: TCodeHelpElement): Boolean;
962 var
963   NewElement: TCodeHelpElement;
964 begin
965   //DebugLn(['TFPDocEditForm.CreateElement ']);
966   if (Element=nil) or (Element.ElementName='') then exit(false);
967   NewElement:=nil;
968   Include(FFlags,fpdefWriting);
969   try
970     Result:=CodeHelpBoss.CreateElement(Element.CodeXYPos.Code,
971                             Element.CodeXYPos.X,Element.CodeXYPos.Y,NewElement);
972   finally
973     Exclude(FFlags,fpdefWriting);
974     NewElement.Free;
975   end;
976   Reset;
977   InvalidateChain;
978 end;
979 
980 procedure TFPDocEditor.UpdateButtons;
981 var
982   HasEdit: Boolean;
983 begin
984   HasEdit:=(PageControl.ActivePage = ShortTabSheet)
985         or (PageControl.ActivePage = DescrTabSheet)
986         or (PageControl.ActivePage = SeeAlsoTabSheet)
987         or (PageControl.ActivePage = ErrorsTabSheet)
988         or (PageControl.ActivePage = TopicSheet);
989   BoldFormatButton.Enabled:=HasEdit;
990   ItalicFormatButton.Enabled:=HasEdit;
991   UnderlineFormatButton.Enabled:=HasEdit;
992   InsertCodeTagButton.Enabled:=HasEdit;
993   InsertLinkSpeedButton.Enabled:=HasEdit;
994   InsertParagraphSpeedButton.Enabled:=HasEdit;
995   InsertRemarkButton.Enabled:=HasEdit;
996   InsertVarTagButton.Enabled:=HasEdit;
997 end;
998 
GetCurrentUnitNamenull999 function TFPDocEditor.GetCurrentUnitName: string;
1000 begin
1001   if (fChain<>nil) and (fChain.Count>0) then
1002     Result:=fChain[0].ElementUnitName
1003   else
1004     Result:='';
1005 end;
1006 
GetCurrentOwnerNamenull1007 function TFPDocEditor.GetCurrentOwnerName: string;
1008 begin
1009   if (fChain<>nil) and (fChain.Count>0) then
1010     Result:=fChain[0].ElementOwnerName
1011   else
1012     Result:='';
1013 end;
1014 
1015 procedure TFPDocEditor.JumpToError(Item: TFPDocItem; LineCol: TPoint);
1016 begin
1017   case Item of
1018   fpdiShort: PageControl.ActivePage:=ShortTabSheet;
1019   fpdiElementLink: PageControl.ActivePage:=InheritedTabSheet;
1020   fpdiDescription:
1021     begin
1022       PageControl.ActivePage:=DescrTabSheet;
1023       DescrSynEdit.CaretXY:=LineCol;
1024     end;
1025   fpdiErrors: PageControl.ActivePage:=ErrorsTabSheet;
1026   fpdiSeeAlso: PageControl.ActivePage:=SeeAlsoTabSheet;
1027   fpdiExample: PageControl.ActivePage:=ExampleTabSheet;
1028   end;
1029 end;
1030 
1031 procedure TFPDocEditor.OpenXML;
1032 var
1033   CurDocFile: TLazFPDocFile;
1034 begin
1035   CurDocFile:=DocFile;
1036   if CurDocFile=nil then exit;
1037   if FileExistsUTF8(CurDocFile.Filename) then begin
1038     LazarusIDE.DoOpenEditorFile(CurDocFile.Filename,-1,-1,
1039       [ofOnlyIfExists,ofRegularFile,ofUseCache]);
1040   end;
1041 end;
1042 
TFPDocEditor.GUIModifiednull1043 function TFPDocEditor.GUIModified: boolean;
1044 begin
1045   if fpdefReading in FFlags then exit(false);
1046   Result:=(ShortEdit.Text<>FOldVisualValues[fpdiShort])
1047     or (LinkEdit.Text<>FOldVisualValues[fpdiElementLink])
1048     or (DescrSynEdit.Text<>FOldVisualValues[fpdiDescription])
1049     or (SeeAlsoSynEdit.Text<>FOldVisualValues[fpdiSeeAlso])
1050     or (ErrorsSynEdit.Text<>FOldVisualValues[fpdiErrors])
1051     or (ExampleEdit.Text<>FOldVisualValues[fpdiExample]);
1052   if Result then begin
1053     if (ShortEdit.Text<>FOldVisualValues[fpdiShort]) then
1054       debugln(['TFPDocEditor.GUIModified Short ',dbgstr(ShortEdit.Text),' <> ',dbgstr(FOldVisualValues[fpdiShort])]);
1055     if (LinkEdit.Text<>FOldVisualValues[fpdiElementLink]) then
1056       debugln(['TFPDocEditor.GUIModified link ',dbgstr(LinkEdit.Text),' <> ',dbgstr(FOldVisualValues[fpdiElementLink])]);
1057     if (DescrSynEdit.Text<>FOldVisualValues[fpdiDescription]) then
1058       debugln(['TFPDocEditor.GUIModified Descr ',dbgstr(DescrSynEdit.Text),' <> ',dbgstr(FOldVisualValues[fpdiDescription])]);
1059     if (SeeAlsoSynEdit.Text<>FOldVisualValues[fpdiSeeAlso]) then
1060       debugln(['TFPDocEditor.GUIModified SeeAlso ',dbgstr(SeeAlsoSynEdit.Text),' <> ',dbgstr(FOldVisualValues[fpdiSeeAlso])]);
1061     if (ErrorsSynEdit.Text<>FOldVisualValues[fpdiErrors]) then
1062       debugln(['TFPDocEditor.GUIModified Errors ',dbgstr(ErrorsSynEdit.Text),' <> ',dbgstr(FOldVisualValues[fpdiErrors])]);
1063     if (ExampleEdit.Text<>FOldVisualValues[fpdiExample]) then
1064       debugln(['TFPDocEditor.GUIModified Example ',dbgstr(ExampleEdit.Text),' <> ',dbgstr(FOldVisualValues[fpdiExample])]);
1065   end;
1066 end;
1067 
1068 procedure TFPDocEditor.DoEditorUpdate(Sender: TObject);
1069 begin
1070   if GetCaptureControl <> nil then // If SynEdit has Capture the user may be selecting by Mouse. https://bugs.freepascal.org/view.php?id=37150
1071     exit;
1072   if FollowCursor then
1073     LoadIdentifierAtCursor;
1074 end;
1075 
1076 procedure TFPDocEditor.DoEditorMouseUp(Sender: TObject);
1077 begin
1078   if FollowCursor then
1079     LoadIdentifierAtCursor;
1080 end;
1081 
1082 procedure TFPDocEditor.UpdateTopicCombo;
1083 var
1084   cnt, i: LongInt;
1085   DFile: TLazFPDocFile;
1086   Topics: TStringList;
1087 begin
1088   Exclude(FFlags,fpdefTopicNeedsUpdate);
1089   Topics:=TStringList.Create;
1090   Include(FFlags,fpdefTopicSettingUp);
1091   try
1092     Dfile := DocFile;
1093     if DFile<>nil then begin
1094       cnt := DFile.GetModuleTopicCount;
1095       for i := 0 to cnt - 1 do
1096         Topics.Add(DFile.GetModuleTopicName(i));
1097     end;
1098     TopicListBox.Items.Assign(Topics);
1099     TopicListBox.ItemIndex:=TopicListBox.Items.IndexOf(FCurrentTopic);
1100     UpdateTopic;
1101   finally
1102     Exclude(FFlags,fpdefTopicSettingUp);
1103     Topics.Free;
1104   end;
1105 end;
1106 
1107 procedure TFPDocEditor.SetIdleConnected(AValue: boolean);
1108 begin
1109   if FIdleConnected=AValue then Exit;
1110   FIdleConnected:=AValue;
1111   if IdleConnected then
1112     Application.AddOnIdleHandler(@ApplicationIdle)
1113   else
1114     Application.RemoveOnIdleHandler(@ApplicationIdle);
1115 end;
1116 
1117 procedure TFPDocEditor.SetFollowCursor(AValue: boolean);
1118 begin
1119   if FFollowCursor=AValue then Exit;
1120   FFollowCursor:=AValue;
1121   if FollowCursor then
1122     LoadIdentifierAtCursor;
1123 end;
1124 
GetDefaultDocFilenull1125 function TFPDocEditor.GetDefaultDocFile(CreateIfNotExists: Boolean): TLazFPDocFile;
1126 var
1127   CacheWasUsed : Boolean;
1128   AnOwner: TObject;
1129   FPDocFileName: String;
1130 begin
1131   Result := nil;
1132   if (not CreateIfNotExists) and (fDocFile<>nil) then
1133     exit(fDocFile);
1134 
1135   FPDocFileName := CodeHelpBoss.GetFPDocFilenameForSource(SourceFilename, true,
1136                                       CacheWasUsed, AnOwner, CreateIfNotExists);
1137   if (FPDocFileName = '')
1138   or (CodeHelpBoss.LoadFPDocFile(FPDocFileName, [chofUpdateFromDisk], Result,
1139                                  CacheWasUsed) <> chprSuccess)
1140   then
1141     Result := nil;
1142 end;
1143 
1144 procedure TFPDocEditor.Reset;
1145 var
1146   i: TFPDocItem;
1147 begin
1148   FreeAndNil(fChain);
1149   if fpdefReading in FFlags then exit;
1150   Include(FFlags,fpdefReading);
1151   try
1152     // clear all element editors/viewers
1153     ShortEdit.Clear;
1154     DescrShortEdit.Clear;
1155     LinkEdit.Clear;
1156     DescrSynEdit.Clear;
1157     SeeAlsoSynEdit.Clear;
1158     ErrorsSynEdit.Clear;
1159     ExampleEdit.Clear;
1160     ClearTopicControls;
1161     for i:=Low(TFPDocItem) to high(TFPDocItem) do
1162       FOldVisualValues[i]:='';
1163 
1164     Modified := False;
1165     //CreateButton.Enabled:=false;
1166     OpenXMLButton.Enabled:=false;
1167   finally
1168     Exclude(FFlags,fpdefReading);
1169   end;
1170 end;
1171 
1172 procedure TFPDocEditor.InvalidateChain;
1173 begin
1174   FreeAndNil(fChain);
1175   FFlags:=FFlags+[fpdefCodeCacheNeedsUpdate,
1176       fpdefChainNeedsUpdate,fpdefCaptionNeedsUpdate,
1177       fpdefValueControlsNeedsUpdate,fpdefInheritedControlsNeedsUpdate];
1178   IdleConnected:=true;
1179 end;
1180 
1181 procedure TFPDocEditor.LoadIdentifierAt(const SrcFilename: string;
1182   const Caret: TPoint);
1183 var
1184   NewSrcFilename: String;
1185 begin
1186   //debugln(['TFPDocEditor.LoadIdentifierAt START ',SrcFilename,' ',dbgs(Caret)]);
1187   // save the current changes to documentation
1188   Save(IsVisible);
1189 
1190   NewSrcFilename:=TrimAndExpandFilename(SrcFilename);
1191   if (NewSrcFilename=SourceFilename) and (CompareCaret(Caret,CaretXY)=0)
1192   and (fChain<>nil) and fChain.IsValid
1193   and (not LazarusIDE.NeedSaveSourceEditorChangesToCodeCache(nil)) then
1194     exit;
1195 
1196   FCaretXY:=Caret;
1197   fSourceFilename:=NewSrcFilename;
1198 
1199   Reset;
1200   Include(FFlags,fpdefTopicNeedsUpdate);
1201   InvalidateChain;
1202 end;
1203 
1204 procedure TFPDocEditor.LoadIdentifierAtCursor;
1205 var
1206   SrcEdit: TSourceEditorInterface;
1207 begin
1208   if SourceEditorManagerIntf=nil then exit;
1209   if csDestroying in ComponentState then exit;
1210   if FFlags*[fpdefReading,fpdefWriting]<>[] then exit;
1211   SrcEdit:=SourceEditorManagerIntf.ActiveEditor;
1212   if SrcEdit=nil then
1213     Reset
1214   else
1215     LoadIdentifierAt(SrcEdit.FileName,SrcEdit.CursorTextXY);
1216 end;
1217 
1218 procedure TFPDocEditor.BeginUpdate;
1219 begin
1220   inc(fUpdateLock);
1221 end;
1222 
1223 procedure TFPDocEditor.EndUpdate;
1224 begin
1225   dec(fUpdateLock);
1226   if fUpdateLock<0 then RaiseGDBException('');
1227   if fUpdateLock=0 then begin
1228     if fpdefCaptionNeedsUpdate in FFlags then UpdateCaption;
1229   end;
1230 end;
1231 
1232 procedure TFPDocEditor.ClearEntry(DoSave: Boolean);
1233 begin
1234   Modified:=true;
1235   ShortEdit.Text:='';
1236   DescrShortEdit.Text:=ShortEdit.Text;
1237   DescrSynEdit.Text:='';
1238   SeeAlsoSynEdit.Text:='';
1239   ErrorsSynEdit.Text:='';
1240   ExampleEdit.Text:='';
1241   if DoSave then Save;
1242 end;
1243 
1244 procedure TFPDocEditor.Save(CheckGUI: boolean);
1245 var
1246   Values: TFPDocElementValues;
1247   TopicDocFile: TLazFPDocFile;
1248   Node: TDOMNode;
1249   Child: TDOMNode;
1250   TopicChanged: Boolean;
1251 begin
1252   //DebugLn(['TFPDocEditor.Save FModified=',FModified]);
1253   if fpdefReading in FFlags then exit;
1254 
1255   if (not FModified)
1256   and ((not CheckGUI) or (not GUIModified)) then
1257   begin
1258     SaveButton.Enabled:=false;
1259     Exit; // nothing changed => exit
1260   end;
1261   //DebugLn(['TFPDocEditor.Save FModified=',FModified,' CheckGUI=',CheckGUI,' GUIModified=',GUIModified]);
1262   FModified:=false;
1263   SaveButton.Enabled:=false;
1264 
1265   TopicChanged:=false;
1266   TopicDocFile:=DocFile;
1267   if FCurrentTopic <> '' then
1268   begin
1269     if fDocFile=nil then
1270       fDocFile := GetDefaultDocFile(True);
1271     TopicDocFile:=DocFile;
1272     if TopicDocFile <> nil then begin
1273       Node := TopicDocFile.GetModuleTopic(FCurrentTopic);
1274       if Node <> nil then begin
1275         Child := Node.FindNode('short');
1276         if (Child = nil)
1277         or (TopicDocFile.GetChildValuesAsString(Child)<>TopicShort.Text)
1278         then begin
1279           TopicDocFile.SetChildValue(Node, 'short', TopicShort.Text);
1280           TopicChanged:=true;
1281         end;
1282         Child := Node.FindNode('descr');
1283         if (Child = nil)
1284         or (TopicDocFile.GetChildValuesAsString(Child)<>TopicDescrSynEdit.Text)
1285         then begin
1286           TopicDocFile.SetChildValue(Node, 'descr', TopicDescrSynEdit.Text);
1287           TopicChanged:=true;
1288         end;
1289       end;
1290     end;
1291   end;
1292   if (fChain=nil) or (fChain.Count=0) then
1293   begin
1294     if IsVisible then
1295       DebugLn(['TFPDocEditor.Save failed: no chain']);
1296   end else if not fChain.IsValid then
1297   begin
1298     if IsVisible then
1299       DebugLn(['TFPDocEditor.Save failed: chain not valid']);
1300   end else if (fChain[0].FPDocFile <> nil) then
1301   begin
1302     Values:=GetGUIValues;
1303     if WriteNode(fChain[0],Values,true) then
1304     begin
1305       // write succeeded
1306       if fChain.DocFile=TopicDocFile then
1307         TopicChanged:=false;
1308     end else begin
1309       DebugLn(['TFPDocEditor.Save WriteNode FAILED']);
1310     end;
1311   end;
1312   if TopicChanged then begin
1313     Include(FFlags,fpdefWriting);
1314     try
1315       CodeHelpBoss.SaveFPDocFile(TopicDocFile);
1316     finally
1317       Exclude(FFlags,fpdefWriting);
1318     end;
1319   end;
1320 end;
1321 
GetGUIValuesnull1322 function TFPDocEditor.GetGUIValues: TFPDocElementValues;
1323 var
1324   i: TFPDocItem;
1325 begin
1326   Result[fpdiShort]:=ShortEdit.Text;
1327   Result[fpdiDescription]:=DescrSynEdit.Text;
1328   Result[fpdiErrors]:=ErrorsSynEdit.Text;
1329   Result[fpdiSeeAlso]:=SeeAlsoSynEdit.Text;
1330   Result[fpdiExample]:=ExampleEdit.Text;
1331   Result[fpdiElementLink]:=LinkEdit.Text;
1332   for i:=Low(TFPDocItem) to High(TFPDocItem) do
1333     if Trim(Result[i])='' then
1334       Result[i]:='';
1335 end;
1336 
1337 procedure TFPDocEditor.SetModified(const AValue: boolean);
1338 begin
1339   if FModified=AValue then exit;
1340   FModified:=AValue;
1341   SaveButton.Enabled:=FModified;
1342   //debugln(['TFPDocEditor.SetModified New=',FModified]);
1343 end;
1344 
1345 procedure TFPDocEditor.UpdateTopic;
1346 var
1347   Child: TDOMNode;
1348   Node: TDOMNode;
1349   DFile: TLazFPDocFile;
1350 begin
1351   FCurrentTopic := '';
1352   try
1353     if TopicListBox.ItemIndex < 0 then exit;
1354     Dfile := GetDefaultDocFile(True);
1355     if DFile = nil then exit;
1356 
1357     FCurrentTopic := TopicListBox.Items[TopicListBox.ItemIndex];
1358     Node := DFile.GetModuleTopic(FCurrentTopic);
1359     if Node = nil then exit;
1360 
1361     Include(FFlags, fpdefTopicSettingUp);
1362     try
1363       Child := Node.FindNode('short');
1364       if Child <> nil then
1365         TopicShort.Text := DFile.GetChildValuesAsString(Child);
1366       Child := Node.FindNode('descr');
1367       if Child <> nil then
1368         TopicDescrSynEdit.Text := DFile.GetChildValuesAsString(Child);
1369       TopicShort.Enabled := True;
1370       TopicDescrSynEdit.Enabled := True;
1371       if TopicShort.IsVisible then
1372         TopicShort.SetFocus;
1373     finally
1374       Exclude(FFlags, fpdefTopicSettingUp);
1375     end;
1376   finally
1377     if FCurrentTopic='' then
1378       ClearTopicControls;
1379   end;
1380 end;
1381 
1382 procedure TFPDocEditor.UpdateShowing;
1383 begin
1384   inherited UpdateShowing;
1385   if IsVisible and (fpdefWasHidden in FFlags) then begin
1386     Exclude(FFlags,fpdefWasHidden);
1387     LoadIdentifierAtCursor;
1388   end;
1389 end;
1390 
1391 procedure TFPDocEditor.Loaded;
1392 begin
1393   inherited Loaded;
1394   DescrSynEdit.ControlStyle:=DescrSynEdit.ControlStyle+[];
1395 end;
1396 
WriteNodenull1397 function TFPDocEditor.WriteNode(Element: TCodeHelpElement;
1398   Values: TFPDocElementValues; Interactive: Boolean): Boolean;
1399 var
1400   TopNode: TDOMNode;
1401   CurDocFile: TLazFPDocFile;
1402   CurDoc: TXMLDocument;
1403 
Checknull1404   function Check(Test: boolean; const  Msg: string): Boolean;
1405   var
1406     CurName: String;
1407   begin
1408     Result:=Test;
1409     if not Test then exit;
1410     DebugLn(['TFPDocEditor.WriteNode ERROR ',Msg]);
1411     if Interactive then begin;
1412       if Element.FPDocFile<>nil then
1413         CurName:=Element.FPDocFile.Filename
1414       else
1415         CurName:=Element.ElementName;
1416       IDEMessageDialog(lisCodeToolsDefsWriteError,
1417         Format(lisFPDocErrorWriting, [CurName, LineEnding, Msg]), mtError, [mbCancel]);
1418     end;
1419   end;
1420 
SetValuenull1421   function SetValue(Item: TFPDocItem): boolean;
1422   var
1423     NewValue: String;
1424   begin
1425     Result:=false;
1426     NewValue:=Values[Item];
1427     try
1428       FixFPDocFragment(NewValue,
1429              Item in [fpdiShort,fpdiDescription,fpdiErrors,fpdiSeeAlso],
1430              true);
1431       CurDocFile.SetChildValue(TopNode,FPDocItemNames[Item],NewValue);
1432       Result:=true;
1433     except
1434       on E: EXMLReadError do begin
1435         DebugLn(['SetValue ',dbgs(E.LineCol),' Name=',FPDocItemNames[Item]]);
1436         JumpToError(Item,E.LineCol);
1437         IDEMessageDialog(lisFPDocFPDocSyntaxError,
1438           Format(lisFPDocThereIsASyntaxErrorInTheFpdocElement, [FPDocItemNames
1439             [Item], LineEnding+LineEnding, E.Message]), mtError, [mbOk], '');
1440       end;
1441     end;
1442   end;
1443 
1444 begin
1445   Result:=false;
1446   if fpdefWriting in FFlags then begin
1447     DebugLn(['TFPDocEditForm.WriteNode inconsistency detected: recursive write']);
1448     exit;
1449   end;
1450 
1451   if Check(Element=nil,'Element=nil') then exit;
1452   CurDocFile:=Element.FPDocFile;
1453   if Check(CurDocFile=nil,'Element.FPDocFile=nil') then begin
1454     // no fpdoc file found
1455     DebugLn(['TFPDocEditForm.WriteNode TODO: implement creating new fpdoc file']);
1456     exit;
1457   end;
1458   CurDoc:=CurDocFile.Doc;
1459   if Check(CurDoc=nil,'Element.FPDocFile.Doc=nil') then exit;
1460   if Check(not Element.ElementNodeValid,'not Element.ElementNodeValid') then exit;
1461   TopNode:=Element.ElementNode;
1462   if Check(TopNode=nil,'TopNode=nil') then begin
1463     // no old node found
1464     Check(false,'no old node found. TODO: implement creating a new.');
1465     Exit;
1466   end;
1467 
1468   Include(FFlags,fpdefWriting);
1469   CurDocFile.BeginUpdate;
1470   try
1471     if SetValue(fpdiShort)
1472     and SetValue(fpdiElementLink)
1473     and SetValue(fpdiDescription)
1474     and SetValue(fpdiErrors)
1475     and SetValue(fpdiSeeAlso)
1476     and SetValue(fpdiExample) then
1477       ;
1478   finally
1479     CurDocFile.EndUpdate;
1480     fChain.MakeValid;
1481     Exclude(FFlags,fpdefWriting);
1482   end;
1483 
1484   if CodeHelpBoss.SaveFPDocFile(CurDocFile)<>mrOk then begin
1485     DebugLn(['TFPDocEditForm.WriteNode failed writing ',CurDocFile.Filename]);
1486     exit;
1487   end;
1488   Result:=true;
1489 end;
1490 
1491 procedure TFPDocEditor.UpdateCodeCache;
1492 begin
1493   if fUpdateLock>0 then begin
1494     Include(FFlags,fpdefCodeCacheNeedsUpdate);
1495     exit;
1496   end;
1497   Exclude(FFlags,fpdefCodeCacheNeedsUpdate);
1498   LazarusIDE.SaveSourceEditorChangesToCodeCache(nil);
1499 end;
1500 
1501 procedure TFPDocEditor.ErrorsSynEditChange(Sender: TObject);
1502 begin
1503   if fpdefReading in FFlags then exit;
1504   if ErrorsSynEdit.Text<>FOldVisualValues[fpdiErrors] then
1505     Modified:=true;
1506 end;
1507 
1508 procedure TFPDocEditor.ExampleEditChange(Sender: TObject);
1509 begin
1510   if fpdefReading in FFlags then exit;
1511   if ExampleEdit.Text<>FOldVisualValues[fpdiExample] then
1512     Modified:=true;
1513 end;
1514 
FindInheritedIndexnull1515 function TFPDocEditor.FindInheritedIndex: integer;
1516 // returns Index in chain of an overriden Element with a short description
1517 // returns -1 if not found
1518 var
1519   Element: TCodeHelpElement;
1520 begin
1521   if (fChain<>nil) then begin
1522     Result:=1;
1523     while (Result<fChain.Count) do begin
1524       Element:=fChain[Result];
1525       if (Element.ElementNode<>nil)
1526       and (Element.FPDocFile.GetValueFromNode(Element.ElementNode,fpdiShort)<>'')
1527       then
1528         exit;
1529       inc(Result);
1530     end;
1531   end;
1532   Result:=-1;
1533 end;
1534 
1535 procedure TFPDocEditor.AddLinkToInheritedButtonClick(Sender: TObject);
1536 var
1537   i: LongInt;
1538   Element: TCodeHelpElement;
1539   Link: String;
1540 begin
1541   i:=FindInheritedIndex;
1542   if i<0 then exit;
1543   //DebugLn(['TFPDocEditor.AddLinkToInheritedButtonClick ']);
1544   Element:=fChain[i];
1545   Link:=Element.ElementName;
1546   if Element.ElementUnitName<>'' then begin
1547     Link:=Element.ElementUnitName+'.'+Link;
1548     if Element.ElementFPDocPackageName<>'' then
1549       Link:='#'+Element.ElementFPDocPackageName+'.'+Link;
1550   end;
1551   if Link<>LinkEdit.Text then begin
1552     LinkEdit.Text:=Link;
1553     Modified:=true;
1554   end;
1555 end;
1556 
1557 procedure TFPDocEditor.BrowseExampleButtonClick(Sender: TObject);
1558 begin
1559   if Doc=nil then exit;
1560   InitIDEFileDialog(OpenDialog);
1561   OpenDialog.Title:=lisChooseAnExampleFile;
1562   OpenDialog.Filter:=dlgFilterPascalFile+'|*.pas;*.pp;*.p|'+dlgFilterAll+'|'+FileMask;
1563   OpenDialog.InitialDir:=ExtractFilePath(DocFile.Filename);
1564   if OpenDialog.Execute then begin
1565     ExampleEdit.Text := ExtractRelativepath(
1566       ExtractFilePath(DocFile.Filename), GetForcedPathDelims(OpenDialog.FileName));
1567     if ExampleEdit.Text<>FOldVisualValues[fpdiExample] then
1568       Modified:=true;
1569   end;
1570   StoreIDEFileDialog(OpenDialog);
1571 end;
1572 
1573 procedure TFPDocEditor.CopyFromInheritedButtonClick(Sender: TObject);
1574 var
1575   i: LongInt;
1576 begin
1577   i:=FindInheritedIndex;
1578   if i<0 then exit;
1579   //DebugLn(['TFPDocEditForm.CopyFromInheritedButtonClick ']);
1580   if ShortEdit.Text<>'' then begin
1581     if IDEQuestionDialog('Confirm replace',
1582       GetContextTitle(fChain[0])+' already contains the help:'+LineEnding+ShortEdit.Text,
1583       mtConfirmation, [mrYes,'Replace',
1584                        mrCancel]) <> mrYes then exit;
1585   end;
1586   LoadGUIValues(fChain[i]);
1587   Modified:=true;
1588 end;
1589 
1590 procedure TFPDocEditor.CopyShortToDescrMenuItemClick(Sender: TObject);
1591 begin
1592   DescrSynEdit.Append(ShortEdit.Text);
1593   Modified:=true;
1594 end;
1595 
1596 procedure TFPDocEditor.CreateButtonClick(Sender: TObject);
1597 begin
1598   if ((fChain=nil) or (fChain.Count=0))
1599   or (TCodeHelpElement(fChain[0]).ElementName='') then begin
1600     IDEMessageDialog('Invalid Declaration','Please place the editor caret on an identifier. If this is a new unit, please save the file first.',
1601       mtError,[mbOK]);
1602     exit;
1603   end;
1604   CreateElement(fChain[0]);
1605 end;
1606 
1607 procedure TFPDocEditor.DescrSynEditChange(Sender: TObject);
1608 begin
1609   if fpdefReading in FFlags then exit;
1610   if DescrSynEdit.Text<>FOldVisualValues[fpdiDescription] then
1611     Modified:=true;
1612 end;
1613 
1614 end.
1615