Subversion Repositories fastphp

Rev

Rev 80 | Rev 85 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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