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