Subversion Repositories fastphp

Rev

Rev 102 | Rev 104 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit EditorMain;
  2.  
  3. {$Include 'FastPHP.inc'}
  4.  
  5. (*
  6.   This program requires
  7.   - Microsoft Internet Controls (TWebBrowser)
  8.     If you are using Delphi 10.1 Starter Edition, please import the ActiveX TLB
  9.     "Microsoft Internet Controls"
  10.   - SynEdit
  11.     You can obtain SynEdit via Embarcadero GetIt
  12. *)
  13.  
  14. // TODO: "crHourGlass" does not work if F9 is pressed!
  15. // TODO: if a scrapfile is already open, create a new scrap file (scrap2.php)
  16. // TODO: localize
  17. // TODO: wieso geht copy paste im twebbrowser nicht???
  18. // TODO: Wieso dauert webbrowser1 erste kompilierung so lange???
  19. // TODO: wieso kommt syntax fehler zweimal? einmal stderr einmal stdout?
  20. // TODO: Browser titlebar (link preview)
  21. // TODO: "jump to next/prev todo" buttons/shortcuts
  22. // TODO: "increase/decrease indent" buttons/shortcuts
  23. // TODO: Solve all compiler warnings, especially in regards Encoding
  24.  
  25. // Note in re Unicode:
  26. // - In Embarcadero® Delphi 10.4 Version 27.0.40680.4203:
  27. //   SynEdit correctly detects UTF-8 files without BOM as well as ANSI files with Umlauts.
  28. //   (Previous versions could not detect UTF-8 files without BOM?!)
  29. // - If BOM is existing, it will be removed. (which is good, because this is defined by PSR-1)
  30.  
  31. // Small things:
  32. // - The scroll bars of SynEdit are not affected by the dark theme
  33. // - dark theme full screen: doubleclick to desktop pixel "0,0" cannot be used to close the app
  34.  
  35. // Future ideas
  36. // - code insight
  37. // - verschiedene php versionen?
  38. // - webbrowser1 nur laden, wenn man den tab anw�hlt?
  39. // - doppelklick auf tab soll diesen schlie�en
  40. // - Onlinehelp (www) aufrufen oder CHM datei
  41. // - Let all colors be adjustable
  42. // - code in bildschirmmitte (horizontal)?
  43. // - search in files of a directory
  44. // - Files in multiple tabs?
  45. // - Configurable tabulator display-width
  46.  
  47. interface
  48.  
  49. uses
  50.   // TODO: "{$IFDEF USE_SHDOCVW_TLB}_TLB{$ENDIF}" does not work with Delphi 10.2
  51.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  52.   Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
  53.   SynEditHighlighter, SynHighlighterPHP, SynEdit, ShDocVw, FindReplace,
  54.   ActnList, SynEditMiscClasses, SynEditSearch, RunPHP, ImgList, SynUnicode,
  55.   System.ImageList, System.Actions, Vcl.Menus, Vcl.Themes, System.UITypes,
  56.   SynEditCodeFolding;
  57.  
  58. {.$DEFINE OnlineHelp}
  59.  
  60. type
  61.   TForm1 = class(TForm)
  62.     PageControl1: TPageControl;
  63.     PlaintextTabSheet: TTabSheet;
  64.     HtmlTabSheet: TTabSheet;
  65.     Memo2: TMemo;
  66.     WebBrowser1: TWebBrowser;
  67.     Splitter1: TSplitter;
  68.     PageControl2: TPageControl;
  69.     CodeTabsheet: TTabSheet;
  70.     HelpTabsheet: TTabSheet;
  71.     WebBrowser2: TWebBrowser;
  72.     OpenDialog1: TOpenDialog;
  73.     Panel1: TPanel;
  74.     OpenDialog3: TOpenDialog;
  75.     SynEdit1: TSynEdit;
  76.     SynPHPSyn1: TSynPHPSyn;
  77.     Panel2: TPanel;
  78.     SynEditFocusTimer: TTimer;
  79.     Button1: TButton;
  80.     Button2: TButton;
  81.     Button3: TButton;
  82.     Button4: TButton;
  83.     Button5: TButton;
  84.     Button6: TButton;
  85.     ActionList: TActionList;
  86.     ActionFind: TAction;
  87.     ActionReplace: TAction;
  88.     ActionFindNext: TAction;
  89.     ActionGoto: TAction;
  90.     ActionSave: TAction;
  91.     ActionHelp: TAction;
  92.     ActionRun: TAction;
  93.     ActionESC: TAction;
  94.     Button7: TButton;
  95.     ActionOpen: TAction;
  96.     Button8: TButton;
  97.     Button9: TButton;
  98.     ActionFindPrev: TAction;
  99.     Timer1: TTimer;
  100.     ActionSpaceToTab: TAction;
  101.     Button11: TButton;
  102.     SynEditSearch1: TSynEditSearch;
  103.     TreeView1: TTreeView;
  104.     Splitter2: TSplitter;
  105.     btnLint: TButton;
  106.     ActionLint: TAction;
  107.     ImageList1: TImageList;
  108.     RunPopup: TPopupMenu;
  109.     OpeninIDE1: TMenuItem;
  110.     ActionRunConsole: TAction;
  111.     Runinconsole1: TMenuItem;
  112.     SavePopup: TPopupMenu;
  113.     Saveas1: TMenuItem;
  114.     Save1: TMenuItem;
  115.     SaveDialog1: TSaveDialog;
  116.     BtnSpecialChars: TImage;
  117.     BtnSpecialCharsOff: TImage;
  118.     BtnSpecialCharsOn: TImage;
  119.     BtnLightOn: TImage;
  120.     BtnLightOff: TImage;
  121.     BtnLight: TImage;
  122.     StartUpTimer: TTimer;
  123.     FileModTimer: TTimer;
  124.     GotoPHPdir1: TMenuItem;
  125.     PHPShell1: TMenuItem;
  126.     ActionSaveAs: TAction;
  127.     ActionGoToPHPDir: TAction;
  128.     ActionPHPInteractiveShell: TAction;
  129.     FontSizeTimer: TTimer;
  130.     procedure Run(Sender: TObject);
  131.     procedure RunConsole(Sender: TObject);
  132.     procedure FormShow(Sender: TObject);
  133.     procedure FormCreate(Sender: TObject);
  134.     procedure FormDestroy(Sender: TObject);
  135.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  136.     procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
  137.     procedure Memo2DblClick(Sender: TObject);
  138.     (*
  139.     {$IFDEF USE_SHDOCVW_TLB}
  140.     *)
  141.     procedure WebBrowser1BeforeNavigate2(ASender: TObject;
  142.       const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  143.       Headers: OleVariant; var Cancel: WordBool);
  144.     (*
  145.     {$ELSE}
  146.     procedure WebBrowser1BeforeNavigate2(ASender: TObject;
  147.       const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  148.       Headers: OleVariant; var Cancel: WordBool);
  149.     {$ENDIF}
  150.     *)
  151.     procedure BeforeNavigate(const URL: OleVariant; var Cancel: WordBool);
  152.     procedure SynEditFocusTimerTimer(Sender: TObject);
  153.     procedure ActionFindExecute(Sender: TObject);
  154.     procedure ActionReplaceExecute(Sender: TObject);
  155.     procedure ActionFindNextExecute(Sender: TObject);
  156.     procedure ActionGotoExecute(Sender: TObject);
  157.     procedure ActionSaveExecute(Sender: TObject);
  158.     procedure ActionHelpExecute(Sender: TObject);
  159.     procedure ActionRunExecute(Sender: TObject);
  160.     procedure ActionESCExecute(Sender: TObject);
  161.     procedure SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  162.       MousePos: TPoint; var Handled: Boolean);
  163.     procedure SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  164.       MousePos: TPoint; var Handled: Boolean);
  165.     procedure ActionOpenExecute(Sender: TObject);
  166.     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  167.     procedure Memo2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  168.     procedure ActionFindPrevExecute(Sender: TObject);
  169.     procedure SynEdit1MouseCursor(Sender: TObject;
  170.       const aLineCharPos: TBufferCoord; var aCursor: TCursor);
  171.     procedure Timer1Timer(Sender: TObject);
  172.     procedure ActionSpaceToTabExecute(Sender: TObject);
  173.     procedure TreeView1DblClick(Sender: TObject);
  174.     procedure SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X, Y,
  175.       Line: Integer; Mark: TSynEditMark);
  176.     procedure SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas;
  177.       TransientType: TTransientType);
  178.     procedure ActionLintExecute(Sender: TObject);
  179.     procedure ActionRunConsoleExecute(Sender: TObject);
  180.     procedure BtnSpecialCharsClick(Sender: TObject);
  181.     procedure WebBrowser1WindowClosing(ASender: TObject;
  182.       IsChildWindow: WordBool; var Cancel: WordBool);
  183.     procedure BtnLightClick(Sender: TObject);
  184.     procedure StartUpTimerTimer(Sender: TObject);
  185.     procedure FileModTimerTimer(Sender: TObject);
  186.     procedure SynEdit1DropFiles(Sender: TObject; X, Y: Integer;
  187.       AFiles: TStrings);
  188.     procedure ActionSaveAsExecute(Sender: TObject);
  189.     procedure ActionGoToPHPDirExecute(Sender: TObject);
  190.     procedure ActionPHPInteractiveShellExecute(Sender: TObject);
  191.     procedure SynEdit1KeyDown(Sender: TObject; var Key: Word;
  192.       Shift: TShiftState);
  193.     procedure FontSizeTimerTimer(Sender: TObject);
  194.     procedure SynEdit1StatusChange(Sender: TObject; Changes: TSynStatusChanges);
  195.   private
  196.     hMutex: THandle;
  197.     CurSearchTerm: string;
  198.     HlpPrevPageIndex: integer;
  199.     SrcRep: TSynEditFindReplace;
  200.     {$IFDEF OnlineHelp}
  201.     gOnlineHelpWord: string;
  202.     {$ENDIF}
  203.     FileModLast: TDateTime;
  204.     FormShowRanOnce: boolean;
  205.     BrowserLoadedOnce: boolean;
  206.     procedure Help;
  207.     function InputRequestCallback(var data: AnsiString): boolean;
  208.     function OutputNotifyCallback(const data: AnsiString): boolean;
  209.     procedure RightTrimAll;
  210.   protected
  211.     ChmIndex: TMemIniFile;
  212.     FScrapFile: string;
  213.     FSaveAsFilename: string;
  214.     codeExplorer: TRunCodeExplorer;
  215.     procedure GotoLineNo(LineNo: integer);
  216.     function GetScrapFile: string;
  217.     procedure StartCodeExplorer;
  218.     procedure RefreshModifySign;
  219.     procedure Theme_Light;
  220.     procedure Theme_Dark;
  221.     function IsThemeDark: boolean;
  222.     function MarkUpLineReference(cont: string): string;
  223.     function ContainsUnicodeCharsInAnsiFile: boolean;
  224.     procedure SaveToFile(filename: string);
  225.   end;
  226.  
  227. var
  228.   Form1: TForm1;
  229.  
  230. implementation
  231.  
  232. {$R *.dfm}
  233.  
  234. {$R Cursors.res}
  235.  
  236. uses
  237.   Functions, StrUtils, WebBrowserUtils, FastPHPUtils, Math, ShellAPI, RichEdit,
  238.   FastPHPTreeView, ImageListEx, FastPHPConfig;
  239.  
  240. const
  241.   crMouseGutter = 1;
  242.  
  243. procedure TForm1.RefreshModifySign;
  244. var
  245.   tmp: string;
  246. begin
  247.   if SynEdit1 = nil then exit;
  248.  
  249.   tmp := Caption;
  250.  
  251.   tmp := StringReplace(tmp, '*', '', [rfReplaceAll]);
  252.   if SynEdit1.Modified then tmp := tmp + '*';
  253.  
  254.   if Caption <> tmp then Caption := tmp;
  255. end;
  256.  
  257. procedure TForm1.ActionFindNextExecute(Sender: TObject);
  258. begin
  259.   SrcRep.FindNext;
  260. end;
  261.  
  262. procedure TForm1.ActionFindPrevExecute(Sender: TObject);
  263. begin
  264.   SrcRep.FindPrev;
  265. end;
  266.  
  267. procedure TForm1.ActionGotoExecute(Sender: TObject);
  268. var
  269.   val: string;
  270.   lineno: integer;
  271. resourcestring
  272.   SGoTo = 'Go to';
  273.   SLineNumber = 'Line number:';
  274. begin
  275.   // TODO: VK_LMENU does not work! only works with AltGr but not Alt
  276.   // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
  277.  
  278.   if not InputQuery(SGoTo, SLineNumber, val) or
  279.      not TryStrToInt(val, lineno) then
  280.   begin
  281.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  282.     exit;
  283.   end;
  284.   GotoLineNo(lineno);
  285. end;
  286.  
  287. procedure TForm1.ActionGoToPHPDirExecute(Sender: TObject);
  288. var
  289.   phpExe: string;
  290. begin
  291.   phpExe := GetPHPExe;
  292.   if phpExe <> '' then
  293.     ShellExecute(Handle, 'open', 'explorer.exe', PChar(ExtractFilePath(phpExe)), '', SW_NORMAL);
  294. end;
  295.  
  296. procedure TForm1.ActionHelpExecute(Sender: TObject);
  297. begin
  298.   Help;
  299.   if PageControl2.ActivePage = HelpTabsheet then
  300.     WebBrowser2.SetFocus
  301.   else if PageControl2.ActivePage = CodeTabsheet then
  302.     SynEdit1.SetFocus;
  303. end;
  304.  
  305. procedure TForm1.ActionLintExecute(Sender: TObject);
  306. begin
  307.   Run(Sender);
  308.   SynEdit1.SetFocus;
  309. end;
  310.  
  311. procedure TForm1.ActionOpenExecute(Sender: TObject);
  312. begin
  313.   If OpenDialog3.Execute then
  314.   begin
  315.     ShellExecute(0, 'open', PChar(ParamStr(0)), PChar('"' + OpenDialog3.FileName + '"'), '', SW_NORMAL);
  316.   end;
  317. end;
  318.  
  319. procedure TForm1.ActionPHPInteractiveShellExecute(Sender: TObject);
  320. var
  321.   phpExe: string;
  322. begin
  323.   phpExe := GetPHPExe;
  324.   if phpExe <> '' then
  325.     ShellExecute(Handle, 'open', PChar(phpExe), '-a', PChar(ExtractFilePath(phpExe)), SW_NORMAL);
  326. end;
  327.  
  328. procedure TForm1.ActionReplaceExecute(Sender: TObject);
  329. begin
  330.   SrcRep.ReplaceExecute;
  331. end;
  332.  
  333. procedure TForm1.ActionRunConsoleExecute(Sender: TObject);
  334. begin
  335.   RunConsole(Sender);
  336.   SynEdit1.SetFocus;
  337. end;
  338.  
  339. procedure TForm1.ActionRunExecute(Sender: TObject);
  340. begin
  341.   Run(Sender);
  342.   SynEdit1.SetFocus;
  343. end;
  344.  
  345. procedure TForm1.RightTrimAll;
  346. var
  347.   i: integer;
  348. begin
  349.   for i := 0 to SynEdit1.Lines.Count-1 do
  350.   begin
  351.     SynEdit1.Lines.Strings[i] := TrimRight(SynEdit1.Lines.Strings[i]);
  352.   end;
  353.  
  354.   (*
  355.   while (SynEdit1.Lines.Count > 0) and (SynEdit1.Lines.Strings[SynEdit1.Lines.Count-1] = '') do
  356.   begin
  357.     SynEdit1.Lines.Delete(SynEdit1.Lines.Count-1);
  358.   end;
  359.   if SynEdit1.SelStart > Length(SynEdit1.Text)-1 then
  360.   begin
  361.     // TODO: This code does not work...
  362.     SynEdit1.SelStart := Length(SynEdit1.Text)-1;
  363.     SynEdit1.SelEnd   := Length(SynEdit1.Text)-1;
  364.   end;
  365.   *)
  366. end;
  367.  
  368. procedure TForm1.ActionSaveAsExecute(Sender: TObject);
  369. var
  370.   hMutexNew: THandle;
  371. resourcestring
  372.   SCannotSaveBecauseMutex = 'Cannot save because file "%s", because it is alrady open in another FastPHP window!';
  373. begin
  374.   if SaveDialog1.Execute then
  375.   begin
  376.     {$REGION 'Switch mutex'}
  377.     hMutexNew := CreateMutex(nil, True, PChar('FastPHP'+md5(UpperCase(SaveDialog1.FileName))));
  378.     if GetLastError = ERROR_ALREADY_EXISTS then
  379.     begin
  380.       ShowMessageFmt(SCannotSaveBecauseMutex, [SaveDialog1.FileName]);
  381.       Close;
  382.     end;
  383.  
  384.     if hMutex <> 0 then CloseHandle(hMutex); // Note: ReleaseMutex does not work as expected!
  385.     hMutex := hMutexNew;
  386.     {$ENDREGION}
  387.  
  388.     FSaveAsFilename := SaveDialog1.FileName;
  389.     Caption := Copy(Caption, 1, Pos(' - ', Caption)-1) + ' - ' + FSaveAsFilename;
  390.     Application.Title := Format('%s - FastPHP', [ExtractFileName(FSaveAsFilename)]); // do not translate!
  391.     Button7.Click;
  392.   end;
  393. end;
  394.  
  395. procedure TForm1.ActionSaveExecute(Sender: TObject);
  396. begin
  397.   RightTrimAll;
  398.   SaveToFile(GetScrapFile);
  399.   SynEdit1.Modified := false;
  400.   RefreshModifySign;
  401.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  402. end;
  403.  
  404. procedure TForm1.ActionSpaceToTabExecute(Sender: TObject);
  405.  
  406.     function SpacesAtBeginning(line: string): integer;
  407.     begin
  408.       result := 0;
  409.       if Trim(line) = '' then exit;
  410.       while line[result+1] = ' ' do
  411.       begin
  412.         inc(result);
  413.       end;
  414.     end;
  415.  
  416.     function GuessIndent(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}): integer;
  417.       function _Check(indent: integer): boolean;
  418.       var
  419.         i: integer;
  420.       begin
  421.         result := true;
  422.         for i := 0 to lines.Count-1 do
  423.           if SpacesAtBeginning(lines.Strings[i]) mod indent <> 0 then
  424.           begin
  425.             // ShowMessageFmt('Zeile "%s" nicht durch %d teilbar!', [lines.strings[i], indent]);
  426.             result := false;
  427.             exit;
  428.           end;
  429.       end;
  430.     var
  431.       i: integer;
  432.     begin
  433.       for i := 8 downto 2 do
  434.       begin
  435.         if _Check(i) then
  436.         begin
  437.           result := i;
  438.           exit;
  439.         end;
  440.       end;
  441.       result := -1;
  442.     end;
  443.  
  444.     procedure SpaceToTab(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}; indent: integer);
  445.     var
  446.       i, spaces: integer;
  447.       newval: string;
  448.       somethingchanged: boolean;
  449.     begin
  450.       somethingchanged := false;
  451.       for i := 0 to lines.Count-1 do
  452.       begin
  453.         spaces := SpacesAtBeginning(lines.Strings[i]);
  454.         newval := StringOfChar(#9, spaces div indent) + StringOfChar(' ', spaces mod indent) + Copy(lines.Strings[i], spaces+1, Length(lines.Strings[i])-spaces);
  455.         if lines.Strings[i] <> newval then
  456.         begin
  457.           somethingchanged := true;
  458.           lines.Strings[i] := newval;
  459.         end;
  460.       end;
  461.       if somethingchanged then
  462.       begin
  463.         RefreshModifySign;
  464.       end;
  465.     end;
  466.  
  467.     function SpacesAvailable(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}): boolean;
  468.     var
  469.       i, spaces: integer;
  470.     begin
  471.       for i := 0 to lines.Count-1 do
  472.       begin
  473.         spaces := SpacesAtBeginning(lines.Strings[i]);
  474.         if spaces > 0 then
  475.         begin
  476.           result := true;
  477.           exit;
  478.         end;
  479.       end;
  480.       result := false;
  481.       exit;
  482.     end;
  483.  
  484. var
  485.   val: string;
  486.   ind: integer;
  487. resourcestring
  488.   SNoLinesAvailable = 'No lines with spaces at the beginning available';
  489.   SSpacesToLabs = 'Spaces to tabs';
  490.   SIndent = 'Indent:';
  491. begin
  492.   // TODO: if something is selected, only process the selected part
  493.  
  494.   if not SpacesAvailable(SynEdit1.Lines) then
  495.   begin
  496.     MessageDlg(SNoLinesAvailable, mtInformation, [mbOk], 0);
  497.     exit;
  498.   end;
  499.  
  500.   ind := GuessIndent(SynEdit1.Lines);
  501.   if ind <> -1 then val := IntToStr(ind);
  502.  
  503.   if not InputQuery(SSpacesToLabs, SIndent, val) or
  504.      not TryStrToInt(Trim(val), ind) then
  505.   begin
  506.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  507.     exit;
  508.   end;
  509.  
  510.   if ind = 0 then exit;
  511.   SpaceToTab(SynEdit1.Lines, ind);
  512. end;
  513.  
  514. procedure TForm1.ActionESCExecute(Sender: TObject);
  515. begin
  516.   if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
  517.      (HelpTabsheet.TabVisible) then
  518.   begin
  519.     PageControl2.ActivePageIndex := HlpPrevPageIndex;
  520.     HelpTabsheet.TabVisible := false;
  521.   end;
  522.  
  523.   // Dirty hack...
  524.   SrcRep.CloseDialogs;
  525. end;
  526.  
  527. procedure TForm1.ActionFindExecute(Sender: TObject);
  528. begin
  529.   SrcRep.FindExecute;
  530. end;
  531.  
  532. procedure TForm1.Run(Sender: TObject);
  533. var
  534.   bakTS: TTabSheet;
  535.   //ss: TStringStream;
  536.   //bakPos: Int64;
  537. begin
  538.   memo2.Lines.Text := '';
  539.  
  540.   if not BrowserLoadedOnce then
  541.   begin
  542.     bakTS := PageControl1.ActivePage;
  543.     try
  544.       PageControl1.ActivePage := HtmlTabSheet; // Required for the first time, otherwise, WebBrowser1.Clear will hang
  545.       Webbrowser1.Clear;
  546.     finally
  547.       PageControl1.ActivePage := bakTS;
  548.     end;
  549.     BrowserLoadedOnce := true;
  550.   end
  551.   else
  552.     Webbrowser1.Clear;
  553.  
  554.   Screen.Cursor := crHourGlass; // TODO: Doesn't work with F9
  555.   Application.ProcessMessages;
  556.  
  557.   try
  558.     ActionSave.Execute; // TODO: if it is not the scrap file: do not save the file, since the user did not intended to save... better create a temporary file and run it instead.
  559.  
  560.     // TODO 70421 * <fastphp> implement flush() with ContentCallBack implementieren... For long running scripts I want to see status changes via javascript which are loaded step by step
  561.     // TODO 70422 * <fastphp> when a script has an endless loop, i want to have a possibility to cancel it
  562.     if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  563.       memo2.Lines.Text := Utf8Decode(RunPHPScript(GetScrapFile, Sender=ActionLint, False)) // if we have a UTF-8 file, then the DOS output is double-UTF8 encoded
  564.     else
  565.       memo2.Lines.Text := RunPHPScript(GetScrapFile, Sender=ActionLint, False);
  566.  
  567.     {$REGION 'Show in Web Browser'}
  568.     if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  569.       Webbrowser1.LoadHTML(MarkUpLineReference('<meta charset="utf-8">'+memo2.Lines.Text), GetScrapFile)
  570.     else
  571.       Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
  572.  
  573.     // Alternatively:
  574.     (*
  575.     ss := TstringStream.Create;
  576.     ss.WriteString(MarkUpLineReference(memo2.Lines.Text));
  577.     ss.Position := 0;
  578.     Webbrowser1.LoadStream(ss, GetScrapFile);
  579.     Webbrowser1.Wait;
  580.     ss.Free;
  581.     *)
  582.     {$ENDREGION}
  583.  
  584.     if IsTextHTML(memo2.lines.text) then
  585.       PageControl1.ActivePage := HtmlTabSheet
  586.     else
  587.       PageControl1.ActivePage := PlaintextTabSheet;
  588.   finally
  589.     Screen.Cursor := crDefault;
  590.   end;
  591. end;
  592.  
  593. procedure TForm1.RunConsole(Sender: TObject);
  594. begin
  595.   ActionSave.Execute; // TODO: if it is not the scrap file: do not save the file, since the user did not intended to save... better create a temporary file and run it instead.
  596.   RunPHPScript(GetScrapFile, Sender=ActionLint, True);
  597. end;
  598.  
  599. procedure TForm1.SynEdit1DropFiles(Sender: TObject; X, Y: Integer;
  600.   AFiles: TStrings);
  601. var
  602.   FileName: string;
  603. const
  604.   WARN_FILE_COUNT = 10;
  605. resourcestring
  606.   SAreYouSure = 'Are you sure you want to open %d files?';
  607. begin
  608.   if AFiles.Count > WARN_FILE_COUNT then
  609.   begin
  610.     if not MessageDlg(Format(SAreYouSure, [WARN_FILE_COUNT]), mtConfirmation, mbYesNoCancel, 0) <> mrYes then
  611.       exit;
  612.   end;
  613.  
  614.   for FileName in AFiles do
  615.   begin
  616.     ShellExecute(0, 'open', PChar(ParamStr(0)), PChar('"' + FileName + '"'), '', SW_NORMAL);
  617.   end;
  618. end;
  619.  
  620. procedure TForm1.SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X,
  621.   Y, Line: Integer; Mark: TSynEditMark);
  622. begin
  623.   (*
  624.   TSynEdit(Sender).CaretX := 1;
  625.   TSynEdit(Sender).CaretY := Line;
  626.   TSynEdit(Sender).SelLength := Length(TSynEdit(Sender).LineText);
  627.   *)
  628. end;
  629.  
  630. procedure TForm1.SynEdit1KeyDown(Sender: TObject; var Key: Word;
  631.   Shift: TShiftState);
  632. begin
  633.   if (Shift = [ssCtrl, ssShift]) and (Key = ord('C')) then
  634.   begin
  635.     // Disable "Column Select Mode" (https://github.com/SynEdit/SynEdit/blob/master/Source/SynEditKeyCmds.pas#L879)
  636.     // which can be enabled by pressing Ctrl+Shift+C
  637.     // Reasons why we disable it:
  638.     // 1. I think nobody needs this, and sometimes you accidentally press it
  639.     // 2. Ctrl+Shift+L would the combination to disable column select mode,
  640.     //    but you cannot use it, because it is already in use for linting
  641.     Key := 0;
  642.   end;
  643. end;
  644.  
  645. procedure TForm1.SynEdit1MouseCursor(Sender: TObject; const aLineCharPos: TBufferCoord; var aCursor: TCursor);
  646. {$IFDEF OnlineHelp}
  647. var
  648.   Line: Integer;
  649.   Column: Integer;
  650.   word: string;
  651. begin
  652.   Line  := aLineCharPos.Line-1;
  653.   Column := aLineCharPos.Char-1;
  654.   word := GetWordUnderPos(TSynEdit(Sender), Line, Column);
  655.   if word <> gOnlineHelpWord then
  656.   begin
  657.     gOnlineHelpWord := word;
  658.     Timer1.Enabled := false;
  659.     Timer1.Enabled := true;
  660.   end;
  661. {$ELSE}
  662. begin
  663. {$ENDIF}
  664. end;
  665.  
  666. procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  667.   MousePos: TPoint; var Handled: Boolean);
  668. begin
  669.   if ssCtrl in Shift then
  670.   begin
  671.     SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
  672.     Handled := true;
  673.   end
  674.   else Handled := false;
  675. end;
  676.  
  677. procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  678.   MousePos: TPoint; var Handled: Boolean);
  679. begin
  680.   if ssCtrl in Shift then
  681.   begin
  682.     SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
  683.     Handled := true;
  684.   end
  685.   else Handled := false;
  686. end;
  687.  
  688. procedure TForm1.SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType);
  689. var
  690.   Editor: TSynEdit;
  691.   OpenChars: array of WideChar;//[0..2] of WideChar=();
  692.   CloseChars: array of WideChar;//[0..2] of WideChar=();
  693.  
  694.   function IsCharBracket(AChar: WideChar): Boolean;
  695.   begin
  696.     case AChar of
  697.       '{','[','(','<','}',']',')','>':
  698.         Result := True;
  699.       else
  700.         Result := False;
  701.     end;
  702.   end;
  703.  
  704.   function CharToPixels(P: TBufferCoord): TPoint;
  705.   begin
  706.     Result := Editor.RowColumnToPixels(Editor.BufferToDisplayPos(P));
  707.   end;
  708.  
  709. var
  710.   COLOR_FG: TColor;
  711.   COLOR_BG: TColor;
  712.   P: TBufferCoord;
  713.   Pix: TPoint;
  714.   D: TDisplayCoord;
  715.   S: UnicodeString;
  716.   I: Integer;
  717.   Attri: TSynHighlighterAttributes;
  718.   ArrayLength: Integer;
  719.   start: Integer;
  720.   TmpCharA, TmpCharB: WideChar;
  721. begin
  722.   // Source: https://github.com/SynEdit/SynEdit/blob/master/Demos/OnPaintTransientDemo/Unit1.pas
  723.  
  724.   if IsThemeDark then
  725.   begin
  726.     COLOR_FG := clLime;
  727.     COLOR_BG := clGreen;
  728.   end
  729.   else
  730.   begin
  731.     COLOR_FG := clGreen;
  732.     COLOR_BG := clLime;
  733.   end;
  734.  
  735.   if TSynEdit(Sender).SelAvail then exit;
  736.   Editor := TSynEdit(Sender);
  737.   ArrayLength:= 3;
  738.  
  739.   (*
  740.   if (Editor.Highlighter = shHTML) or (Editor.Highlighter = shXML) then
  741.     inc(ArrayLength);
  742.   *)
  743.  
  744.   SetLength(OpenChars, ArrayLength);
  745.   SetLength(CloseChars, ArrayLength);
  746.   for i := 0 to ArrayLength - 1 do
  747.   begin
  748.     case i of
  749.       0: begin OpenChars[i] := '('; CloseChars[i] := ')'; end;
  750.       1: begin OpenChars[i] := '{'; CloseChars[i] := '}'; end;
  751.       2: begin OpenChars[i] := '['; CloseChars[i] := ']'; end;
  752.       3: begin OpenChars[i] := '<'; CloseChars[i] := '>'; end;
  753.     end;
  754.   end;
  755.  
  756.   P := Editor.CaretXY;
  757.   D := Editor.DisplayXY;
  758.  
  759.   Start := Editor.SelStart;
  760.  
  761.   if (Start > 0) and (Start <= length(Editor.Text)) then
  762.     TmpCharA := Editor.Text[Start]
  763.   else
  764.     TmpCharA := #0;
  765.  
  766.   if (Start > 0){Added by VTS} and (Start < length(Editor.Text)) then
  767.     TmpCharB := Editor.Text[Start + 1]
  768.   else
  769.     TmpCharB := #0;
  770.  
  771.   if not IsCharBracket(TmpCharA) and not IsCharBracket(TmpCharB) then exit;
  772.   S := TmpCharB;
  773.   if not IsCharBracket(TmpCharB) then
  774.   begin
  775.     P.Char := P.Char - 1;
  776.     S := TmpCharA;
  777.   end;
  778.   Editor.GetHighlighterAttriAtRowCol(P, S, Attri);
  779.  
  780.   if (Editor.Highlighter.SymbolAttribute = Attri) then
  781.   begin
  782.     for i := low(OpenChars) to High(OpenChars) do
  783.     begin
  784.       if (S = OpenChars[i]) or (S = CloseChars[i]) then
  785.       begin
  786.         Pix := CharToPixels(P);
  787.  
  788.         Editor.Canvas.Brush.Style := bsSolid;//Clear;
  789.         Editor.Canvas.Font.Assign(Editor.Font);
  790.         Editor.Canvas.Font.Style := Attri.Style;
  791.  
  792.         if (TransientType = ttAfter) then
  793.         begin
  794.           Editor.Canvas.Font.Color := COLOR_FG;
  795.           Editor.Canvas.Brush.Color := COLOR_BG;
  796.         end
  797.         else
  798.         begin
  799.           Editor.Canvas.Font.Color := Attri.Foreground;
  800.           Editor.Canvas.Brush.Color := Attri.Background;
  801.         end;
  802.         if Editor.Canvas.Font.Color = clNone then
  803.           Editor.Canvas.Font.Color := Editor.Font.Color;
  804.         if Editor.Canvas.Brush.Color = clNone then
  805.           Editor.Canvas.Brush.Color := Editor.Color;
  806.  
  807.         Editor.Canvas.TextOut(Pix.X, Pix.Y, S);
  808.         P := Editor.GetMatchingBracketEx(P);
  809.  
  810.         if (P.Char > 0) and (P.Line > 0) then
  811.         begin
  812.           Pix := CharToPixels(P);
  813.           if Pix.X > Editor.Gutter.Width then
  814.           begin
  815.             {$REGION 'Added by ViaThinkSoft'}
  816.             if (TransientType = ttAfter) then
  817.             begin
  818.               Editor.Canvas.Font.Color := COLOR_FG;
  819.               Editor.Canvas.Brush.Color := COLOR_BG;
  820.             end
  821.             else
  822.             begin
  823.               Editor.Canvas.Font.Color := Attri.Foreground;
  824.               Editor.Canvas.Brush.Color := Attri.Background;
  825.             end;
  826.             if Editor.Canvas.Font.Color = clNone then
  827.               Editor.Canvas.Font.Color := Editor.Font.Color;
  828.             if Editor.Canvas.Brush.Color = clNone then
  829.               Editor.Canvas.Brush.Color := Editor.Color;
  830.             {$ENDREGION}
  831.             if S = OpenChars[i] then
  832.               Editor.Canvas.TextOut(Pix.X, Pix.Y, CloseChars[i])
  833.             else Editor.Canvas.TextOut(Pix.X, Pix.Y, OpenChars[i]);
  834.           end;
  835.         end;
  836.       end;
  837.     end;
  838.     Editor.Canvas.Brush.Style := bsSolid;
  839.   end;
  840. end;
  841.  
  842. procedure TForm1.SynEdit1StatusChange(Sender: TObject;
  843.   Changes: TSynStatusChanges);
  844. begin
  845.   if scModified in Changes then
  846.     RefreshModifySign;
  847. end;
  848.  
  849. procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
  850. begin
  851.   SynEditFocusTimer.Enabled := false;
  852.   Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
  853.   SynEdit1.SetFocus;
  854. end;
  855.  
  856. procedure TForm1.Theme_Dark;
  857. begin
  858.   if IsThemeDark then exit;
  859.   TStyleManager.TrySetStyle('Windows10 SlateGray'); // do not translate
  860.   Color := 1316887;
  861.   Font.Color := clCream;
  862.   //Memo2.Font.Color := clCream;
  863.   //Memo2.ParentColor := true;
  864.   SynEdit1.ActiveLineColor := 2238502;
  865.   SynEdit1.Color := 1316887;
  866.   SynEdit1.Font.Color := clCream;
  867.   SynEdit1.Gutter.Color := 1316887;
  868.   SynEdit1.Gutter.Font.Color := clCream;
  869.   SynEdit1.Gutter.GradientStartColor := 2238502;
  870.   SynEdit1.Gutter.GradientEndColor := 1316887;
  871.   SynPHPSyn1.CommentAttri.Foreground := $00837B82;
  872.   SynPHPSyn1.IdentifierAttri.Foreground := 9627120;
  873.   SynPHPSyn1.KeyAttri.Foreground := 4157595;
  874.   SynPHPSyn1.NumberAttri.Foreground := 5008079;
  875.   SynPHPSyn1.StringAttri.Foreground := 6987151;
  876.   SynPHPSyn1.SymbolAttri.Foreground := 8769754;
  877.   SynPHPSyn1.VariableAttri.Foreground := 6924493;
  878. end;
  879.  
  880. procedure TForm1.Theme_Light;
  881. begin
  882.   if not IsThemeDark then exit;
  883.   TStyleManager.TrySetStyle('Windows'); // do not translate
  884.   Color := clBtnFace;
  885.   Font.Color := clWindowText;
  886.   //Memo2.Font.Color := clWindowText;
  887.   SynEdit1.ActiveLineColor := 14680010;
  888.   SynEdit1.Color := clCream;
  889.   SynEdit1.Font.Color := clWindowText;
  890.   SynEdit1.Gutter.Color := clBtnFace;
  891.   SynEdit1.Gutter.Font.Color := clWindowText;
  892.   SynEdit1.Gutter.GradientStartcolor := cl3dLight;
  893.   SynEdit1.Gutter.GradientEndColor := clBtnFace;;
  894.   SynPHPSyn1.CommentAttri.Foreground := 33023;
  895.   SynPHPSyn1.IdentifierAttri.Foreground := 4194304;
  896.   SynPHPSyn1.KeyAttri.Foreground := 4227072;
  897.   SynPHPSyn1.NumberAttri.Foreground := 213;
  898.   SynPHPSyn1.StringAttri.Foreground := 13762560;
  899.   SynPHPSyn1.SymbolAttri.Foreground := 4227072;
  900.   SynPHPSyn1.VariableAttri.Foreground := 213;
  901. end;
  902.  
  903. procedure TForm1.Timer1Timer(Sender: TObject);
  904. begin
  905.   {$IFDEF OnlineHelp}
  906.   Timer1.Enabled := false;
  907.  
  908.   // TODO: Insert a small online help hint
  909.   //Caption := gOnlineHelpWord;
  910.   {$ENDIF}
  911. end;
  912.  
  913. procedure TForm1.TreeView1DblClick(Sender: TObject);
  914. var
  915.   tn: TTreeNode;
  916.   lineNo: integer;
  917. begin
  918.   tn := TTreeView(Sender).Selected;
  919.   if tn = nil then exit;
  920.   lineNo := Integer(tn.Data);
  921.   if lineNo > 0 then GotoLineNo(lineNo);
  922. end;
  923.  
  924. (*
  925. {$IFDEF USE_SHDOCVW_TLB}
  926. *)
  927. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  928.   const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  929.   Headers: OleVariant; var Cancel: WordBool);
  930. begin
  931.   BeforeNavigate(URL, Cancel);
  932. end;
  933. (*
  934. {$ELSE}
  935. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  936.   const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  937.   Headers: OleVariant; var Cancel: WordBool);
  938. begin
  939.   BeforeNavigate(URL, Cancel);
  940. end;
  941. {$ENDIF}
  942. *)
  943.  
  944. procedure TForm1.WebBrowser1WindowClosing(ASender: TObject;
  945.   IsChildWindow: WordBool; var Cancel: WordBool);
  946. resourcestring
  947.   SCloseRequest = 'A script has requested the window to be closed. The window of a standalone script would now close.';
  948. begin
  949.   ShowMessage(SCloseRequest);
  950.   TWebBrowser(ASender).Clear;
  951.   Cancel := true;
  952. end;
  953.  
  954. procedure TForm1.BeforeNavigate(const URL: OleVariant; var Cancel: WordBool);
  955. var
  956.   s, myURL: string;
  957.   lineno: integer;
  958.   p: integer;
  959. begin
  960.   {$REGION 'Line number references (PHP errors and warnings)'}
  961.   if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
  962.   begin
  963.     try
  964.       s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
  965.       if not TryStrToInt(s, lineno) then exit;
  966.       GotoLineNo(lineno);
  967.       SynEditFocusTimer.Enabled := true;
  968.     finally
  969.       Cancel := true;
  970.     end;
  971.     Exit;
  972.   end;
  973.   {$ENDREGION}
  974.  
  975.   {$REGION 'Intelligent browser (executes PHP scripts which are clicked in a hyperlink)'}
  976.   if URL <> 'about:blank' then
  977.   begin
  978.     myUrl := URL;
  979.  
  980.     p := Pos('?', myUrl);
  981.     if p >= 1 then myURL := copy(myURL, 1, p-1);
  982.  
  983.     // TODO: myURL urldecode
  984.     // TODO: maybe we could even open that file in the editor!
  985.     // TODO: ?parameter=....
  986.  
  987.     if FileExists(myURL) and (EndsText('.php', myURL) or EndsText('.php3', myURL) or EndsText('.php4', myURL) or EndsText('.php5', myURL) or EndsText('.phps', myURL)) then
  988.     begin
  989.       WebBrowser1.LoadHTML(RunPHPScript(myURL), myUrl);
  990.       Cancel := true;
  991.     end;
  992.   end;
  993.   {$ENDREGION}
  994. end;
  995.  
  996. procedure TForm1.BtnLightClick(Sender: TObject);
  997. var
  998.   CanClose: boolean;
  999. begin
  1000.   FormCloseQuery(Form1, CanClose);
  1001.   if not CanClose then exit;
  1002.  
  1003.   if IsThemeDark then
  1004.   begin
  1005.     BtnLight.Picture.Assign(BtnLightOn.Picture);
  1006.     Theme_Light;
  1007.     TFastPHPConfig.DarkTheme := false;
  1008.   end
  1009.   else
  1010.   begin
  1011.     BtnLight.Picture.Assign(BtnLightOff.Picture);
  1012.     Theme_Dark;
  1013.     TFastPHPConfig.DarkTheme := true;
  1014.   end;
  1015. end;
  1016.  
  1017. procedure TForm1.BtnSpecialCharsClick(Sender: TObject);
  1018. var
  1019.   opts: TSynEditorOptions;
  1020. begin
  1021.   opts := SynEdit1.Options;
  1022.   if eoShowSpecialChars in SynEdit1.Options then
  1023.   begin
  1024.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOff.Picture);
  1025.     Exclude(opts, eoShowSpecialChars);
  1026.     TFastPHPConfig.SpecialChars := false;
  1027.   end
  1028.   else
  1029.   begin
  1030.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOn.Picture);
  1031.     Include(opts, eoShowSpecialChars);
  1032.     TFastPHPConfig.SpecialChars := true;
  1033.   end;
  1034.   SynEdit1.Options := opts;
  1035. end;
  1036.  
  1037. procedure TForm1.FileModTimerTimer(Sender: TObject);
  1038. resourcestring
  1039.   SChangeConflict = 'The file was changed in a different application BUT IT WAS ALSO MODIFIED HERE! Reload file AND LOSE CHANGES HERE?';
  1040.   SChangeNotify = 'The file was changed in a different application! Reload file?';
  1041. begin
  1042.   FileModTimer.Enabled := false;
  1043.   if FileModLast <> FileAge(GetScrapFile) then
  1044.   begin
  1045.     FileModLast := FileAge(GetScrapFile);
  1046.     if SynEdit1.Modified then
  1047.     begin
  1048.       if MessageDlg(SChangeConflict, mtWarning, mbYesNoCancel, 0) = mrYes then
  1049.       begin
  1050.         SynEdit1.Lines.LoadFromFile(GetScrapFile);
  1051.       end;
  1052.     end
  1053.     else
  1054.     begin
  1055.       if MessageDlg(SChangeNotify, mtConfirmation, mbYesNoCancel, 0) = mrYes then
  1056.       begin
  1057.         SynEdit1.Lines.LoadFromFile(GetScrapFile);
  1058.       end;
  1059.     end;
  1060.   end;
  1061.   FileModTimer.Enabled := true;
  1062. end;
  1063.  
  1064. procedure TForm1.FontSizeTimerTimer(Sender: TObject);
  1065. begin
  1066.   if Memo2.Font.Size <> (SynEdit1.Font.Size-3) then
  1067.     Memo2.Font.Size := (SynEdit1.Font.Size-3);
  1068. end;
  1069.  
  1070. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  1071. begin
  1072.   DragAcceptFiles(Handle, False);
  1073.   TFastPHPConfig.FontSize := SynEdit1.Font.Size;
  1074. end;
  1075.  
  1076. procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  1077. var
  1078.   r: integer;
  1079. resourcestring
  1080.   SWantToSave = 'Do you want to save?';
  1081. begin
  1082.   if SynEdit1.Modified then
  1083.   begin
  1084.     if (ParamStr(1) <> '') or (FSaveAsFilename <> '') then
  1085.     begin
  1086.       r := MessageDlg(SWantToSave, mtConfirmation, mbYesNoCancel, 0);
  1087.       if r = mrCancel then
  1088.       begin
  1089.         CanClose := false;
  1090.         Exit;
  1091.       end
  1092.       else if r = mrYes then
  1093.       begin
  1094.         ActionSave.Execute;
  1095.         CanClose := true;
  1096.       end;
  1097.     end
  1098.     else
  1099.     begin
  1100.       ActionSave.Execute;
  1101.       CanClose := true;
  1102.     end;
  1103.   end;
  1104. end;
  1105.  
  1106. procedure TForm1.FormCreate(Sender: TObject);
  1107. var
  1108.   exeDir: string;
  1109.   sScrapFile: string;
  1110. begin
  1111.   HlpPrevPageIndex := -1;
  1112.   CurSearchTerm := '';
  1113.   sScrapFile := GetScrapFile;
  1114.   Caption := Caption + ' - ' + sScrapFile;
  1115.   Application.Title := Format('%s - FastPHP', [ExtractFileName(sScrapFile)]); // do not translate!
  1116.   SrcRep := TSynEditFindReplace.Create(self);
  1117.   SrcRep.Editor := SynEdit1;
  1118.   SynEdit1.Gutter.Gradient := HighColorWindows;
  1119.  
  1120.   Screen.Cursors[crMouseGutter] := LoadCursor(hInstance, 'MOUSEGUTTER');
  1121.   SynEdit1.Gutter.Cursor := crMouseGutter;
  1122.  
  1123.   exeDir := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
  1124.   if FileExists(exeDir + 'codeexplorer.bmp') then ImageList1.LoadAndSplitImages(exeDir + 'codeexplorer.bmp');
  1125.  
  1126.   FileModLast := FileAge(sScrapFile);
  1127.   FileModTimer.Enabled := True;
  1128. end;
  1129.  
  1130. procedure TForm1.FormDestroy(Sender: TObject);
  1131. begin
  1132.   if Assigned(ChmIndex) then
  1133.   begin
  1134.     FreeAndNil(ChmIndex);
  1135.   end;
  1136.   FreeAndNil(SrcRep);
  1137.  
  1138.   if hMutex <> 0 then CloseHandle(hMutex); // Note: ReleaseMutex does not work as expected!
  1139.  
  1140.   if Assigned(codeExplorer) then
  1141.   begin
  1142.     codeExplorer.Terminate;
  1143.     codeExplorer.WaitFor;
  1144.     FreeAndNil(codeExplorer);
  1145.   end;
  1146. end;
  1147.  
  1148. procedure TForm1.FormShow(Sender: TObject);
  1149. var
  1150.   ScrapFile: string;
  1151.   tmpFontSize: integer;
  1152.   opts: TSynEditorOptions;
  1153. resourcestring
  1154.   SFileAlreadyOpen = 'File "%s" is alrady open!';
  1155. begin
  1156.   if FormShowRanOnce then exit; // If the theme is changed from normal to dark, OnShow will be called another time
  1157.   FormShowRanOnce := true;
  1158.  
  1159.   ScrapFile := GetScrapFile;
  1160.   if ScrapFile = '' then
  1161.   begin
  1162.     Application.Terminate; // Close;
  1163.     exit;
  1164.   end;
  1165.  
  1166.   opts := SynEdit1.Options;
  1167.   if TFastPHPConfig.SpecialChars then
  1168.   begin
  1169.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOn.Picture);
  1170.     Include(opts, eoShowSpecialChars);
  1171.   end
  1172.   else
  1173.   begin
  1174.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOff.Picture);
  1175.     Exclude(opts, eoShowSpecialChars);
  1176.   end;
  1177.   SynEdit1.Options := opts;
  1178.  
  1179.   if FileExists(ScrapFile) then
  1180.   begin
  1181.     if hMutex = 0 then
  1182.     begin
  1183.       hMutex := CreateMutex(nil, True, PChar('FastPHP'+md5(UpperCase(ScrapFile))));
  1184.       if GetLastError = ERROR_ALREADY_EXISTS then
  1185.       begin
  1186.         // TODO: It would be great if the window of that FastPHP instance would switched to foreground
  1187.         ShowMessageFmt(SFileAlreadyOpen, [ScrapFile]);
  1188.         Close;
  1189.       end;
  1190.  
  1191.       SynEdit1.Lines.LoadFromFile(ScrapFile);
  1192.     end;
  1193.   end
  1194.   else
  1195.     SynEdit1.Lines.Clear;
  1196.  
  1197.   PageControl1.ActivePage := PlaintextTabSheet;
  1198.  
  1199.   PageControl2.ActivePage := CodeTabsheet;
  1200.   HelpTabsheet.TabVisible := false;
  1201.  
  1202.   tmpFontSize := TFastPHPConfig.FontSize;
  1203.   if tmpFontSize <> -1 then SynEdit1.Font.Size := tmpFontSize;
  1204.   SynEdit1.SetFocus;
  1205.  
  1206.   DoubleBuffered := true;
  1207.   StartCodeExplorer;
  1208.  
  1209.   DragAcceptFiles(Handle, True);
  1210.  
  1211.   StartupTimer.Enabled := true;
  1212. end;
  1213.  
  1214. function TForm1.ContainsUnicodeCharsInAnsiFile: boolean;
  1215. var
  1216.   lines: TStringList;
  1217.   ms: TMemoryStream;
  1218. begin
  1219.   if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  1220.   begin
  1221.     result := false;
  1222.     exit;
  1223.   end;
  1224.  
  1225.   lines := TStringList.Create;
  1226.   ms := TMemoryStream.Create;
  1227.   try
  1228.     SynEdit1.Lines.SaveToStream(ms);
  1229.     ms.Position := 0;
  1230.     lines.LoadFromStream(ms);
  1231.     result := Trim(lines.Text) <> Trim(synedit1.Lines.Text);
  1232.   finally
  1233.     FreeAndNil(ms);
  1234.     FreeAndNil(lines);
  1235.   end;
  1236. end;
  1237.  
  1238. procedure TForm1.SaveToFile(filename: string);
  1239. var
  1240.   ss: TStringStream;
  1241.   ms: TMemoryStream;
  1242.   fs: TFileStream;
  1243.   eolStyle: string;
  1244.   str: string;
  1245.   UpgradeEncodingToUnicode: boolean;
  1246.   DoReloadEncoding: boolean;
  1247. resourcestring
  1248.   LNG_UpgradeToUtf8 = 'Unicode characters have been inserted to this ANSI encoded file. Convert file to UTF-8? (If you choose "No", the Unicode characters will be replaced with "?")';
  1249. begin
  1250.   FileModTimer.Enabled := false;
  1251.  
  1252.   if ContainsUnicodeCharsInAnsiFile then
  1253.   begin
  1254.     DoReloadEncoding := true;
  1255.     case MessageBox(Handle, PChar(LNG_UpgradeToUtf8), PChar(Caption), MB_YESNOCANCEL) of
  1256.       ID_YES:
  1257.       begin
  1258.         UpgradeEncodingToUnicode := true;
  1259.       end;
  1260.       ID_NO:
  1261.       begin
  1262.         UpgradeEncodingToUnicode := false;
  1263.       end;
  1264.       ID_CANCEL:
  1265.       begin
  1266.         Abort;
  1267.       end;
  1268.     end;
  1269.   end
  1270.   else
  1271.   begin
  1272.     DoReloadEncoding := false;
  1273.     UpgradeEncodingToUnicode := false;
  1274.   end;
  1275.  
  1276.   ms := TMemoryStream.Create;
  1277.   ss := TStringStream.Create('');
  1278.   fs := TFileStream.Create(filename, fmCreate);
  1279.   try
  1280.     // Save everything in a memory stream and then to a string
  1281.     // in comparison to "str := SynEdit1.Lines.Text;",
  1282.     // This approach should preserve LF / CRLF line endings
  1283.     if UpgradeEncodingToUnicode then
  1284.       SynEdit1.Lines.SaveToStream(ms, TEncoding.UTF8) // upgrade an ANSI file to UTF-8 (e.g. if you include Japanese characters)
  1285.     else
  1286.       SynEdit1.Lines.SaveToStream(ms);
  1287.     if DoReloadEncoding then
  1288.     begin
  1289.       ms.Position := 0;
  1290.       SynEdit1.Lines.LoadFromStream(ms);
  1291.     end;
  1292.     ms.Position := 0;
  1293.     ss.CopyFrom(ms, ms.Size);
  1294.     ss.Position := 0;
  1295.     str := ss.ReadString(ss.Size);
  1296.     ss.Size := 0; // clear string-stream, because we need it later again
  1297.  
  1298.     // Detect current line-endings
  1299.     if Copy(str, 1, 2) = '#!' then
  1300.     begin
  1301.       // Shebang. Use ONLY Linux LF
  1302.       str := StringReplace(str, #13#10, #10, [rfReplaceAll]);
  1303.       eolStyle := #10 // Linux LF
  1304.     end
  1305.     else
  1306.     begin
  1307.       if Pos(#13#10, str) > 0 then
  1308.         eolStyle := #13#10 // Windows CRLF
  1309.       else if Pos(#10, str) > 0 then
  1310.         eolStyle := #10 // Linux LF
  1311.       else
  1312.       begin
  1313.         if DefaultTextLineBreakStyle = tlbsLF then
  1314.           eolStyle := #10 // Linux LF
  1315.         else if DefaultTextLineBreakStyle = tlbsCRLF then
  1316.           eolStyle := #13#10 // Windows CRLF
  1317.         //else if DefaultTextLineBreakStyle = tlbsCR then
  1318.         //  eolStyle := #13 // Old Mac CR
  1319.         else
  1320.           eolStyle := #13#10; // (Should not happen)
  1321.       end;
  1322.     end;
  1323.  
  1324.     // Unitfy line-endings
  1325.     str := StringReplace(str, #13, '', [rfReplaceAll]);
  1326.     str := StringReplace(str, #10, eolStyle, [rfReplaceAll]);
  1327.  
  1328.     // Replace all trailing linebreaks by a single line break
  1329.     // Note: Removing all line breaks is not good, since Linux's "nano" will
  1330.     //       re-add a linebreak at the end of the file
  1331.     str := TrimRight(str) + eolStyle;
  1332.  
  1333.     // Old versions of Delphi/SynEdit write an UTF-8 BOM, which makes problems
  1334.     // e.g. with AJAX handlers (because AJAX reponses must not have a BOM).
  1335.     // So we try to avoid that.
  1336.     // Note that the output is still UTF-8 encoded if the input file was UTF-8 encoded
  1337.     if Copy(str,1,3) = #$EF#$BB#$BF then Delete(str, 1, 3);
  1338.  
  1339.     // Now save to the file
  1340.     ss.WriteString(str);
  1341.     ss.Position := 0;
  1342.     fs.CopyFrom(ss, ss.Size-ss.Position);
  1343.   finally
  1344.     FreeAndNil(ms);
  1345.     FreeAndNil(ss);
  1346.     FreeAndNil(fs);
  1347.   end;
  1348.  
  1349.   FileModLast := FileAge(GetScrapFile);
  1350.   FileModTimer.Enabled := True;
  1351. end;
  1352.  
  1353. procedure TForm1.StartCodeExplorer;
  1354. begin
  1355.   codeExplorer := TRunCodeExplorer.Create(true);
  1356.   codeExplorer.InputRequestCallback := InputRequestCallback;
  1357.   codeExplorer.OutputNotifyCallback := OutputNotifyCallback;
  1358.   codeExplorer.PhpExe := GetPHPExe;
  1359.   codeExplorer.PhpFile := IncludeTrailingPathDelimiter(ExtractFileDir(Application.ExeName)) + 'codeexplorer.php'; // GetScrapFile;
  1360.   codeExplorer.WorkDir := ExtractFileDir(Application.ExeName);
  1361.   codeExplorer.Resume;
  1362. end;
  1363.  
  1364. procedure TForm1.StartUpTimerTimer(Sender: TObject);
  1365. begin
  1366.   StartupTimer.Enabled := false;
  1367.  
  1368.   // We need this timer because we cannot change the Theme during OnShow,
  1369.   // because the Delphi VCL Theme is buggy!
  1370.  
  1371.   if TFastPHPConfig.DarkTheme then
  1372.   begin
  1373.     BtnLight.Picture.Assign(BtnLightOff.Picture);
  1374.     Theme_Dark;
  1375.   end
  1376.   else
  1377.   begin
  1378.     BtnLight.Picture.Assign(BtnLightOn.Picture);
  1379.     Theme_Light;
  1380.   end;
  1381. end;
  1382.  
  1383. function TForm1.GetScrapFile: string;
  1384. var
  1385.   tmpPath: string;
  1386. resourcestring
  1387.   SFileDoesNotExistsCreate = 'File %s does not exist. Create it?';
  1388.   SPathDoesNotExistTryAgain = 'Path does not exist! Please try again.';
  1389. begin
  1390.   if FSaveAsFilename <> '' then
  1391.   begin
  1392.     result := FSaveAsFilename;
  1393.     exit;
  1394.   end;
  1395.  
  1396.   if FScrapFile <> '' then
  1397.   begin
  1398.     result := FScrapFile;
  1399.     exit;
  1400.   end;
  1401.  
  1402.   if ParamStr(1) <> '' then
  1403.   begin
  1404.     // Program was started with a filename
  1405.  
  1406.     result := ParamStr(1);
  1407.  
  1408.     if not FileExists(result) then
  1409.     begin
  1410.       case MessageDlg(Format(SFileDoesNotExistsCreate, [result]), mtConfirmation, mbYesNoCancel, 0) of
  1411.         mrYes:
  1412.           try
  1413.             SaveToFile(result);
  1414.           except
  1415.             on E: Exception do
  1416.             begin
  1417.               MessageDlg(E.Message, mtError, [mbOk], 0);
  1418.               Application.Terminate;
  1419.               result := '';
  1420.               exit;
  1421.             end;
  1422.           end;
  1423.         mrNo:
  1424.           begin
  1425.             Application.Terminate;
  1426.             result := '';
  1427.             exit;
  1428.           end;
  1429.         mrCancel:
  1430.           begin
  1431.             Application.Terminate;
  1432.             result := '';
  1433.             exit;
  1434.           end;
  1435.       end;
  1436.     end;
  1437.   end
  1438.   else
  1439.   begin
  1440.     // Program is started without filename -> use scrap file
  1441.  
  1442.     result := TFastPHPConfig.ScrapFile;
  1443.  
  1444.     if not FileExists(result) then
  1445.     begin
  1446.       repeat
  1447.         {$REGION 'Determinate opendialog initial directory'}
  1448.         if result <> '' then
  1449.         begin
  1450.           tmpPath := ExtractFilePath(result);
  1451.           if DirectoryExists(tmpPath) then
  1452.           begin
  1453.             OpenDialog3.InitialDir := tmpPath;
  1454.             OpenDialog3.FileName := Result;
  1455.           end
  1456.           else
  1457.           begin
  1458.             OpenDialog3.InitialDir := GetMyDocumentsFolder;
  1459.           end;
  1460.         end
  1461.         else
  1462.         begin
  1463.           OpenDialog3.InitialDir := GetMyDocumentsFolder;
  1464.         end;
  1465.         {$ENDREGION}
  1466.  
  1467.         if not OpenDialog3.Execute then
  1468.         begin
  1469.           Application.Terminate;
  1470.           result := '';
  1471.           exit;
  1472.         end;
  1473.  
  1474.         if not DirectoryExists(ExtractFilePath(OpenDialog3.FileName)) then
  1475.         begin
  1476.           MessageDlg(SPathDoesNotExistTryAgain, mtWarning, [mbOk], 0);
  1477.         end
  1478.         else
  1479.         begin
  1480.           result := OpenDialog3.FileName;
  1481.         end;
  1482.       until result <> '';
  1483.  
  1484.       if not FileExists(result) then
  1485.       begin
  1486.         try
  1487.           // Try saving the file; check if we have permissions
  1488.           //SynEdit1.Lines.Clear;
  1489.           SaveToFile(result);
  1490.         except
  1491.           on E: Exception do
  1492.           begin
  1493.             MessageDlg(E.Message, mtError, [mbOk], 0);
  1494.             Application.Terminate;
  1495.             result := '';
  1496.             exit;
  1497.           end;
  1498.         end;
  1499.       end;
  1500.  
  1501.       TFastPHPConfig.ScrapFile := result;
  1502.       FScrapFile := result;
  1503.     end;
  1504.   end;
  1505. end;
  1506.  
  1507. procedure TForm1.Help;
  1508. var
  1509.   IndexFile, chmFile, w, OriginalWord, url: string;
  1510.   internalHtmlFile: string;
  1511. resourcestring
  1512.   SChmFileNotAValidPHPDocumentation = 'The CHM file is not a valid PHP documentation. Cannot use help.';
  1513.   SUnknownErrorCannotUseHelp = 'Unknown error. Cannot use help.';
  1514.   SNoHelpAvailable = 'No help for "%s" available';
  1515. begin
  1516.   if not Assigned(ChmIndex) then
  1517.   begin
  1518.     IndexFile := TFastPHPConfig.HelpIndex;
  1519.     IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
  1520.     if FileExists(IndexFile) then
  1521.     begin
  1522.       ChmIndex := TMemIniFile.Create(IndexFile);
  1523.     end;
  1524.   end;
  1525.  
  1526.   if Assigned(ChmIndex) then
  1527.   begin
  1528.     IndexFile := TFastPHPConfig.HelpIndex;
  1529.     // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
  1530.  
  1531.     chmFile := ChangeFileExt(IndexFile, '.chm');
  1532.     if not FileExists(chmFile) then
  1533.     begin
  1534.       FreeAndNil(ChmIndex);
  1535.     end;
  1536.   end;
  1537.  
  1538.   if not Assigned(ChmIndex) then
  1539.   begin
  1540.     if not OpenDialog1.Execute then exit;
  1541.  
  1542.     chmFile := OpenDialog1.FileName;
  1543.     if not FileExists(chmFile) then exit;
  1544.  
  1545.     IndexFile := ChangeFileExt(chmFile, '.ini');
  1546.  
  1547.     if not FileExists(IndexFile) then
  1548.     begin
  1549.       Panel1.Align := alClient;
  1550.       Panel1.Visible := true;
  1551.       Panel1.BringToFront;
  1552.       Screen.Cursor := crHourGlass;
  1553.       Application.ProcessMessages;
  1554.       try
  1555.         if not ParseCHM(chmFile) then
  1556.         begin
  1557.           MessageDlg(SChmFileNotAValidPHPDocumentation, mtError, [mbOk], 0);
  1558.           exit;
  1559.         end;
  1560.       finally
  1561.         Screen.Cursor := crDefault;
  1562.         Panel1.Visible := false;
  1563.       end;
  1564.  
  1565.       if not FileExists(IndexFile) then
  1566.       begin
  1567.         MessageDlg(SUnknownErrorCannotUseHelp, mtError, [mbOk], 0);
  1568.         exit;
  1569.       end;
  1570.     end;
  1571.  
  1572.     TFastPHPConfig.HelpIndex := IndexFile;
  1573.  
  1574.     ChmIndex := TMemIniFile.Create(IndexFile);
  1575.   end;
  1576.  
  1577.   w := GetWordUnderCaret(SynEdit1);
  1578.   if w = '' then exit;
  1579.   {$IFDEF UNICODE}
  1580.   if CharInSet(w[1], ['0'..'9']) then exit;
  1581.   {$ELSE}
  1582.   if w[1] in ['0'..'9'] then exit;
  1583.   {$ENDIF}
  1584.  
  1585.   Originalword := w;
  1586. //  w := StringReplace(w, '_', '-', [rfReplaceAll]);
  1587.   w := LowerCase(w);
  1588.   CurSearchTerm := w;
  1589.  
  1590.   internalHtmlFile := ChmIndex.ReadString('function', CurSearchTerm, ''); // do not translate
  1591.   if internalHtmlFile = '' then
  1592.     internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, ''); // do not translate
  1593.   if internalHtmlFile = '' then
  1594.   begin
  1595.     HelpTabsheet.TabVisible := false;
  1596.     HlpPrevPageIndex := -1;
  1597.     ShowMessageFmt(SNoHelpAvailable, [Originalword]);
  1598.     Exit;
  1599.   end;
  1600.  
  1601.   url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
  1602.  
  1603.   HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
  1604.   HelpTabsheet.TabVisible := true;
  1605.   PageControl2.ActivePage := HelpTabsheet;
  1606.   WebBrowser2.Navigate(url);
  1607.   WebBrowser2.Wait;
  1608. end;
  1609.  
  1610. procedure TForm1.GotoLineNo(LineNo:integer);
  1611. var
  1612.   line: string;
  1613.   i: integer;
  1614. begin
  1615.   SynEdit1.GotoLineAndCenter(LineNo);
  1616.  
  1617.   // Skip indent
  1618.   line := SynEdit1.Lines[SynEdit1.CaretY];
  1619.   for i := 1 to Length(line) do
  1620.   begin
  1621.     {$IFDEF UNICODE}
  1622.     if not CharInSet(line[i], [' ', #9]) then
  1623.     {$ELSE}
  1624.     if not (line[i] in [' ', #9]) then
  1625.     {$ENDIF}
  1626.     begin
  1627.       SynEdit1.CaretX := i-1;
  1628.       break;
  1629.     end;
  1630.   end;
  1631.  
  1632.   PageControl2.ActivePage := CodeTabsheet;
  1633.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  1634. end;
  1635.  
  1636. procedure TForm1.PageControl2Changing(Sender: TObject;
  1637.   var AllowChange: Boolean);
  1638. begin
  1639.   if PageControl2.ActivePage = HelpTabsheet then
  1640.     HlpPrevPageIndex := -1
  1641.   else
  1642.     HlpPrevPageIndex := PageControl2.ActivePageIndex;
  1643.  
  1644.   AllowChange := true;
  1645. end;
  1646.  
  1647. procedure TForm1.Memo2DblClick(Sender: TObject);
  1648. var
  1649.   line: string;
  1650.  
  1651.   procedure _process(toFind: string);
  1652.   var
  1653.     p, lineno: integer;
  1654.   begin
  1655.     if FileSystemCaseSensitive then
  1656.       p := Pos(toFind, line)
  1657.     else
  1658.       p := Pos(LowerCase(toFind), LowerCase(line));
  1659.     if p <> 0 then
  1660.     begin
  1661.       line := copy(line, p+length(toFind), 99); // TODO: "(test.php:123)" does not work, because "123)" will be tried to convert to Integer
  1662.       if not TryStrToInt(line, lineno) then exit;
  1663.       GotoLineNo(lineno);
  1664.     end;
  1665.   end;
  1666.  
  1667. begin
  1668.   line := memo2.Lines.Strings[Memo2.CaretPos.Y];
  1669.  
  1670.   {$REGION 'Possibility 1: filename.php:lineno'}
  1671.   _process(ExtractFileName(GetScrapFile) + ':');
  1672.   {$ENDREGION}
  1673.  
  1674.   {$REGION 'Possibility 2: on line xx'}
  1675.   _process(ExtractFileName(GetScrapFile) + ' on line '); // do not translate!
  1676.   {$ENDREGION}
  1677. end;
  1678.  
  1679. procedure TForm1.Memo2KeyDown(Sender: TObject; var Key: Word;
  1680.   Shift: TShiftState);
  1681. begin
  1682.   if ((ssCtrl in Shift) and (Key = 65)) then TMemo(Sender).SelectAll;
  1683. end;
  1684.  
  1685. function TForm1.MarkUpLineReference(cont: string): string;
  1686.  
  1687.   procedure _process(toFind: string);
  1688.   var
  1689.     p, a, b: integer;
  1690.     num: integer;
  1691.     insert_a, insert_b: string;
  1692.   begin
  1693.     if FileSystemCaseSensitive then
  1694.       p := Pos(toFind, cont)
  1695.     else
  1696.       p := Pos(LowerCase(toFind), LowerCase(cont));
  1697.     while p >= 1 do
  1698.     begin
  1699.       a := p;
  1700.       b := p + length(toFind);
  1701.       num := 0;
  1702.       {$IFDEF UNICODE}
  1703.       while CharInSet(cont[b], ['0'..'9']) do
  1704.       {$ELSE}
  1705.       while cont[b] in ['0'..'9'] do
  1706.       {$ENDIF}
  1707.       begin
  1708.         num := num*10 + StrToInt(cont[b]);
  1709.         inc(b);
  1710.       end;
  1711.  
  1712.       insert_b := '</a>';
  1713.       insert_a := '<a href="' + FASTPHP_GOTO_URI_PREFIX + IntToStr(num) + '">';
  1714.  
  1715.       insert(insert_b, cont, b);
  1716.       insert(insert_a, cont, a);
  1717.  
  1718.       p := b + Length(insert_a) + Length(insert_b);
  1719.  
  1720.       p := PosEx(toFind, cont, p+1);
  1721.     end;
  1722.   end;
  1723.  
  1724. begin
  1725.   {$REGION 'Possibility 1: filename.php:lineno'}
  1726.   _process(ExtractFileName(GetScrapFile) + ':');
  1727.   {$ENDREGION}
  1728.  
  1729.   {$REGION 'Possibility 2: on line xx'}
  1730.   _process(ExtractFileName(GetScrapFile) + ' on line '); // do not translate!
  1731.   {$ENDREGION}
  1732.  
  1733.   result := cont;
  1734. end;
  1735.  
  1736. function TForm1.InputRequestCallback(var data: AnsiString): boolean;
  1737. begin
  1738.   data := UTF8Encode(SynEdit1.Text);
  1739.   result := true;
  1740. end;
  1741.  
  1742. function TForm1.IsThemeDark: boolean;
  1743. begin
  1744.   result := Assigned(TStyleManager.ActiveStyle) and (TStyleManager.ActiveStyle.Name<>'Windows');
  1745. end;
  1746.  
  1747. function TForm1.OutputNotifyCallback(const data: AnsiString): boolean;
  1748. begin
  1749.   result := TreeView1.FillWithFastPHPData(string(data));
  1750. end;
  1751.  
  1752. end.
  1753.