Subversion Repositories fastphp

Rev

Rev 31 | Rev 33 | 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
 
2 daniel-mar 14
// TODO: localize
15
// TODO: wieso geht copy paste im twebbrowser nicht???
16
// Wieso dauert webbrowser1 erste kompilierung so lange???
5 daniel-mar 17
// TODO: wieso kommt syntax fehler zweimal? einmal stderr einmal stdout?
18
// TODO: Browser titlebar (link preview)
21 daniel-mar 19
// TODO: todo liste
2 daniel-mar 20
 
21
// Future ideas
31 daniel-mar 22
// - code insight
2 daniel-mar 23
// - verschiedene php versionen?
24
// - webbrowser1 nur laden, wenn man den tab anwählt?
25
// - doppelklick auf tab soll diesen schließen
5 daniel-mar 26
// - Onlinehelp (www) aufrufen
13 daniel-mar 27
// - Let all colors be adjustable
21 daniel-mar 28
// - code in bildschirmmitte (horizontal)?
2 daniel-mar 29
 
30
interface
31
 
32
uses
27 daniel-mar 33
  // TODO: "{$IFDEF USE_SHDOCVW_TLB}_TLB{$ENDIF}" does not work with Delphi 10.2
2 daniel-mar 34
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
4 daniel-mar 35
  Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
27 daniel-mar 36
  SynEditHighlighter, SynHighlighterPHP, SynEdit, ShDocVw_TLB, FindReplace,
37
  System.Actions, Vcl.ActnList, System.UITypes, SynEditMiscClasses,
38
  SynEditSearch, RunPHP;
2 daniel-mar 39
 
23 daniel-mar 40
{.$DEFINE OnlineHelp}
41
 
2 daniel-mar 42
type
43
  TForm1 = class(TForm)
44
    PageControl1: TPageControl;
45
    PlaintextTabSheet: TTabSheet;
46
    HtmlTabSheet: TTabSheet;
47
    Memo2: TMemo;
48
    WebBrowser1: TWebBrowser;
49
    Splitter1: TSplitter;
50
    PageControl2: TPageControl;
20 daniel-mar 51
    CodeTabsheet: TTabSheet;
2 daniel-mar 52
    HelpTabsheet: TTabSheet;
53
    WebBrowser2: TWebBrowser;
54
    OpenDialog1: TOpenDialog;
55
    Panel1: TPanel;
56
    OpenDialog3: TOpenDialog;
4 daniel-mar 57
    SynEdit1: TSynEdit;
58
    SynPHPSyn1: TSynPHPSyn;
5 daniel-mar 59
    Panel2: TPanel;
60
    SynEditFocusTimer: TTimer;
61
    Button1: TButton;
62
    Button2: TButton;
63
    Button3: TButton;
13 daniel-mar 64
    Button4: TButton;
65
    Button5: TButton;
66
    Button6: TButton;
67
    ActionList: TActionList;
68
    ActionFind: TAction;
69
    ActionReplace: TAction;
70
    ActionFindNext: TAction;
71
    ActionGoto: TAction;
72
    ActionSave: TAction;
73
    ActionHelp: TAction;
74
    ActionRun: TAction;
75
    ActionESC: TAction;
76
    Button7: TButton;
15 daniel-mar 77
    ActionOpen: TAction;
78
    Button8: TButton;
22 daniel-mar 79
    Button9: TButton;
80
    ActionFindPrev: TAction;
23 daniel-mar 81
    Timer1: TTimer;
82
    ActionSpaceToTab: TAction;
83
    Button11: TButton;
24 daniel-mar 84
    SynEditSearch1: TSynEditSearch;
27 daniel-mar 85
    TreeView1: TTreeView;
26 daniel-mar 86
    Splitter2: TSplitter;
2 daniel-mar 87
    procedure Run(Sender: TObject);
88
    procedure FormShow(Sender: TObject);
89
    procedure FormCreate(Sender: TObject);
90
    procedure FormDestroy(Sender: TObject);
91
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
92
    procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
5 daniel-mar 93
    procedure Memo2DblClick(Sender: TObject);
94
    procedure WebBrowser1BeforeNavigate2(ASender: TObject;
27 daniel-mar 95
      const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
5 daniel-mar 96
      Headers: OleVariant; var Cancel: WordBool);
97
    procedure SynEditFocusTimerTimer(Sender: TObject);
13 daniel-mar 98
    procedure ActionFindExecute(Sender: TObject);
99
    procedure ActionReplaceExecute(Sender: TObject);
100
    procedure ActionFindNextExecute(Sender: TObject);
101
    procedure ActionGotoExecute(Sender: TObject);
102
    procedure ActionSaveExecute(Sender: TObject);
103
    procedure ActionHelpExecute(Sender: TObject);
104
    procedure ActionRunExecute(Sender: TObject);
105
    procedure ActionESCExecute(Sender: TObject);
106
    procedure SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
107
      MousePos: TPoint; var Handled: Boolean);
108
    procedure SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
109
      MousePos: TPoint; var Handled: Boolean);
15 daniel-mar 110
    procedure ActionOpenExecute(Sender: TObject);
111
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
17 daniel-mar 112
    procedure Memo2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
22 daniel-mar 113
    procedure ActionFindPrevExecute(Sender: TObject);
23 daniel-mar 114
    procedure SynEdit1MouseCursor(Sender: TObject;
115
      const aLineCharPos: TBufferCoord; var aCursor: TCursor);
116
    procedure Timer1Timer(Sender: TObject);
117
    procedure ActionSpaceToTabExecute(Sender: TObject);
27 daniel-mar 118
    procedure TreeView1DblClick(Sender: TObject);
30 daniel-mar 119
    procedure SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X, Y,
120
      Line: Integer; Mark: TSynEditMark);
31 daniel-mar 121
    procedure SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas;
122
      TransientType: TTransientType);
2 daniel-mar 123
  private
124
    CurSearchTerm: string;
125
    HlpPrevPageIndex: integer;
24 daniel-mar 126
    SrcRep: TSynEditFindReplace;
23 daniel-mar 127
    {$IFDEF OnlineHelp}
128
    gOnlineHelpWord: string;
129
    {$ENDIF}
2 daniel-mar 130
    procedure Help;
5 daniel-mar 131
    function MarkUpLineReference(cont: string): string;
31 daniel-mar 132
    function InputRequestCallback: AnsiString;
133
    procedure OutputNotifyCallback(const data: AnsiString);
2 daniel-mar 134
  protected
135
    ChmIndex: TMemIniFile;
19 daniel-mar 136
    FScrapFile: string;
27 daniel-mar 137
    codeExplorer: TRunCodeExplorer;
138
    procedure GotoLineNo(LineNo: integer);
2 daniel-mar 139
    function GetScrapFile: string;
27 daniel-mar 140
    procedure StartCodeExplorer;
2 daniel-mar 141
  end;
142
 
143
var
144
  Form1: TForm1;
145
 
146
implementation
147
 
148
{$R *.dfm}
149
 
30 daniel-mar 150
{$R Cursors.res}
151
 
2 daniel-mar 152
uses
25 daniel-mar 153
  Functions, StrUtils, WebBrowserUtils, FastPHPUtils, Math, ShellAPI, RichEdit,
27 daniel-mar 154
  FastPHPTreeView;
2 daniel-mar 155
 
30 daniel-mar 156
const
157
  crMouseGutter = 1;
158
 
13 daniel-mar 159
// TODO: FindPrev ?
160
procedure TForm1.ActionFindNextExecute(Sender: TObject);
161
begin
162
  SrcRep.FindNext;
163
end;
164
 
22 daniel-mar 165
procedure TForm1.ActionFindPrevExecute(Sender: TObject);
166
begin
167
  SrcRep.FindPrev;
168
end;
169
 
13 daniel-mar 170
procedure TForm1.ActionGotoExecute(Sender: TObject);
5 daniel-mar 171
var
172
  val: string;
173
  lineno: integer;
174
begin
13 daniel-mar 175
  // TODO: VK_LMENU does not work! only works with AltGr but not Alt
176
  // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
5 daniel-mar 177
 
13 daniel-mar 178
  InputQuery('Go to', 'Line number:', val);
179
  if not TryStrToInt(val, lineno) then
180
  begin
181
    if SynEdit1.CanFocus then SynEdit1.SetFocus;
182
    exit;
183
  end;
184
  GotoLineNo(lineno);
185
end;
5 daniel-mar 186
 
13 daniel-mar 187
procedure TForm1.ActionHelpExecute(Sender: TObject);
188
begin
189
  Help;
190
  if PageControl2.ActivePage = HelpTabsheet then
191
    WebBrowser2.SetFocus
20 daniel-mar 192
  else if PageControl2.ActivePage = CodeTabsheet then
13 daniel-mar 193
    SynEdit1.SetFocus;
194
end;
8 daniel-mar 195
 
15 daniel-mar 196
procedure TForm1.ActionOpenExecute(Sender: TObject);
197
begin
198
  If OpenDialog3.Execute then
199
  begin
200
    ShellExecute(0, 'open', PChar(ParamStr(0)), PChar(OpenDialog3.FileName), '', SW_NORMAL);
201
  end;
202
end;
203
 
13 daniel-mar 204
procedure TForm1.ActionReplaceExecute(Sender: TObject);
205
begin
206
  SrcRep.ReplaceExecute;
207
end;
5 daniel-mar 208
 
13 daniel-mar 209
procedure TForm1.ActionRunExecute(Sender: TObject);
210
begin
211
  Run(Sender);
212
  SynEdit1.SetFocus;
213
end;
5 daniel-mar 214
 
13 daniel-mar 215
procedure TForm1.ActionSaveExecute(Sender: TObject);
216
begin
27 daniel-mar 217
  SynEdit1.Lines.SaveToFile(GetScrapFile);
16 daniel-mar 218
  SynEdit1.Modified := false;
13 daniel-mar 219
end;
220
 
23 daniel-mar 221
procedure TForm1.ActionSpaceToTabExecute(Sender: TObject);
222
 
223
    function SpacesAtBeginning(line: string): integer;
224
    begin
27 daniel-mar 225
      if line.Trim = '' then exit(0);
23 daniel-mar 226
      result := 0;
227
      while line[result+1] = ' ' do
228
      begin
229
        inc(result);
230
      end;
231
    end;
232
 
27 daniel-mar 233
    function GuessIndent(lines: TStrings): integer;
23 daniel-mar 234
      function _Check(indent: integer): boolean;
235
      var
236
        i: integer;
237
      begin
238
        result := true;
239
        for i := 0 to lines.Count-1 do
240
          if SpacesAtBeginning(lines.Strings[i]) mod indent <> 0 then
241
          begin
242
            // ShowMessageFmt('Zeile "%s" nicht durch %d teilbar!', [lines.strings[i], indent]);
27 daniel-mar 243
            exit(false);
23 daniel-mar 244
          end;
245
      end;
246
    var
247
      i: integer;
248
    begin
249
      for i := 8 downto 2 do
250
      begin
27 daniel-mar 251
        if _Check(i) then exit(i);
23 daniel-mar 252
      end;
253
      result := -1;
254
    end;
255
 
27 daniel-mar 256
    procedure SpaceToTab(lines: TStrings; indent: integer);
23 daniel-mar 257
    var
258
      i, spaces: integer;
259
    begin
260
      for i := 0 to lines.Count-1 do
261
      begin
262
        spaces := SpacesAtBeginning(lines.Strings[i]);
263
        lines.Strings[i] := StringOfChar(#9, spaces div indent) + StringOfChar(' ', spaces mod indent) + Copy(lines.Strings[i], spaces+1, Length(lines.Strings[i])-spaces);
264
      end;
265
    end;
266
 
27 daniel-mar 267
    function SpacesAvailable(lines: TStrings): boolean;
23 daniel-mar 268
    var
269
      i, spaces: integer;
270
    begin
271
      for i := 0 to lines.Count-1 do
272
      begin
273
        spaces := SpacesAtBeginning(lines.Strings[i]);
27 daniel-mar 274
        if spaces > 0 then exit(true);
23 daniel-mar 275
      end;
27 daniel-mar 276
      exit(false);
23 daniel-mar 277
    end;
278
 
279
var
280
  val: string;
281
  ind: integer;
282
resourcestring
283
  SNoLinesAvailable = 'No lines with spaces at the beginning available';
284
begin
285
  // TODO: if something is selected, only process the selected part
286
 
287
  if not SpacesAvailable(SynEdit1.Lines) then
288
  begin
289
    ShowMessage(SNoLinesAvailable);
290
    exit;
291
  end;
292
 
293
  ind := GuessIndent(SynEdit1.Lines);
294
  if ind <> -1 then val := IntToStr(ind);
295
 
296
  InputQuery('Spaces to tabs', 'Indent:', val); // TODO: handle CANCEL correctly...
27 daniel-mar 297
  if TryStrToInt(val.Trim, ind) then
23 daniel-mar 298
  begin
299
    if ind = 0 then exit;
300
    SpaceToTab(SynEdit1.Lines, ind);
301
  end;
302
 
303
  if SynEdit1.CanFocus then SynEdit1.SetFocus;
304
end;
305
 
13 daniel-mar 306
procedure TForm1.ActionESCExecute(Sender: TObject);
307
begin
308
  if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
309
     (HelpTabsheet.TabVisible) then
310
  begin
311
    PageControl2.ActivePageIndex := HlpPrevPageIndex;
312
    HelpTabsheet.TabVisible := false;
2 daniel-mar 313
  end;
13 daniel-mar 314
 
315
  // Dirty hack...
22 daniel-mar 316
  SrcRep.CloseDialogs;
2 daniel-mar 317
end;
318
 
13 daniel-mar 319
procedure TForm1.ActionFindExecute(Sender: TObject);
320
begin
321
  SrcRep.FindExecute;
322
end;
323
 
16 daniel-mar 324
var
325
  firstTimeBrowserLoad: boolean = true;
2 daniel-mar 326
procedure TForm1.Run(Sender: TObject);
16 daniel-mar 327
var
328
  bakTS: TTabSheet;
2 daniel-mar 329
begin
5 daniel-mar 330
  memo2.Lines.Text := '';
16 daniel-mar 331
 
332
  if firstTimeBrowserLoad then
333
  begin
334
    bakTS := PageControl1.ActivePage;
335
    try
336
      PageControl1.ActivePage := HtmlTabSheet; // Required for the first time, otherwise, WebBrowser1.Clear will hang
337
      Webbrowser1.Clear;
338
    finally
339
      PageControl1.ActivePage := bakTS;
340
    end;
341
    firstTimeBrowserLoad := false;
342
  end
343
  else
344
    Webbrowser1.Clear;
345
 
5 daniel-mar 346
  Screen.Cursor := crHourGlass;
347
  Application.ProcessMessages;
348
 
349
  try
27 daniel-mar 350
    SynEdit1.Lines.SaveToFile(GetScrapFile);
5 daniel-mar 351
 
8 daniel-mar 352
    memo2.Lines.Text := RunPHPScript(GetScrapFile);
5 daniel-mar 353
 
8 daniel-mar 354
    Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
5 daniel-mar 355
 
356
    if IsTextHTML(memo2.lines.text) then
357
      PageControl1.ActivePage := HtmlTabSheet
358
    else
359
      PageControl1.ActivePage := PlaintextTabSheet;
360
  finally
361
    Screen.Cursor := crDefault;
2 daniel-mar 362
  end;
5 daniel-mar 363
end;
2 daniel-mar 364
 
30 daniel-mar 365
procedure TForm1.SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X,
366
  Y, Line: Integer; Mark: TSynEditMark);
367
begin
368
  (*
369
  TSynEdit(Sender).CaretX := 1;
370
  TSynEdit(Sender).CaretY := Line;
371
  TSynEdit(Sender).SelLength := Length(TSynEdit(Sender).LineText);
372
  *)
373
end;
374
 
23 daniel-mar 375
procedure TForm1.SynEdit1MouseCursor(Sender: TObject; const aLineCharPos: TBufferCoord; var aCursor: TCursor);
376
{$IFDEF OnlineHelp}
377
var
378
  Line: Integer;
379
  Column: Integer;
380
  word: string;
381
begin
382
  Line  := aLineCharPos.Line-1;
383
  Column := aLineCharPos.Char-1;
384
  word := GetWordUnderPos(TSynEdit(Sender), Line, Column);
385
  if word <> gOnlineHelpWord then
386
  begin
387
    gOnlineHelpWord := word;
388
    Timer1.Enabled := false;
389
    Timer1.Enabled := true;
390
  end;
391
{$ELSE}
392
begin
393
{$ENDIF}
394
end;
395
 
13 daniel-mar 396
procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
397
  MousePos: TPoint; var Handled: Boolean);
398
begin
399
  if ssCtrl in Shift then
400
  begin
401
    SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
23 daniel-mar 402
    Handled := true;
403
  end
404
  else Handled := false;
13 daniel-mar 405
end;
406
 
407
procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
408
  MousePos: TPoint; var Handled: Boolean);
409
begin
410
  if ssCtrl in Shift then
411
  begin
412
    SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
23 daniel-mar 413
    Handled := true;
414
  end
415
  else Handled := false;
13 daniel-mar 416
end;
417
 
31 daniel-mar 418
procedure TForm1.SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType);
419
var
420
  Editor: TSynEdit;
421
  OpenChars: array of WideChar;//[0..2] of WideChar=();
422
  CloseChars: array of WideChar;//[0..2] of WideChar=();
423
 
424
  function IsCharBracket(AChar: WideChar): Boolean;
425
  begin
426
    case AChar of
427
      '{','[','(','<','}',']',')','>':
428
        Result := True;
429
      else
430
        Result := False;
431
    end;
432
  end;
433
 
434
  function CharToPixels(P: TBufferCoord): TPoint;
435
  begin
436
    Result := Editor.RowColumnToPixels(Editor.BufferToDisplayPos(P));
437
  end;
438
 
439
const
440
  COLOR_FG = clRed;
441
  COLOR_BG = clInfoBk;
442
var
443
  P: TBufferCoord;
444
  Pix: TPoint;
445
  D: TDisplayCoord;
446
  S: UnicodeString;
447
  I: Integer;
448
  Attri: TSynHighlighterAttributes;
449
  ArrayLength: Integer;
450
  start: Integer;
451
  TmpCharA, TmpCharB: WideChar;
452
begin
453
  // Source: https://github.com/SynEdit/SynEdit/blob/master/Demos/OnPaintTransientDemo/Unit1.pas
454
 
455
  if TSynEdit(Sender).SelAvail then exit;
456
  Editor := TSynEdit(Sender);
457
  ArrayLength:= 3;
458
 
459
  (*
460
  if (Editor.Highlighter = shHTML) or (Editor.Highlighter = shXML) then
461
    inc(ArrayLength);
462
  *)
463
 
464
  SetLength(OpenChars, ArrayLength);
465
  SetLength(CloseChars, ArrayLength);
466
  for i := 0 to ArrayLength - 1 do
467
  begin
468
    case i of
469
      0: begin OpenChars[i] := '('; CloseChars[i] := ')'; end;
470
      1: begin OpenChars[i] := '{'; CloseChars[i] := '}'; end;
471
      2: begin OpenChars[i] := '['; CloseChars[i] := ']'; end;
472
      3: begin OpenChars[i] := '<'; CloseChars[i] := '>'; end;
473
    end;
474
  end;
475
 
476
  P := Editor.CaretXY;
477
  D := Editor.DisplayXY;
478
 
479
  Start := Editor.SelStart;
480
 
481
  if (Start > 0) and (Start <= length(Editor.Text)) then
482
    TmpCharA := Editor.Text[Start]
483
  else
484
    TmpCharA := #0;
485
 
486
  if (Start < length(Editor.Text)) then
487
    TmpCharB := Editor.Text[Start + 1]
488
  else
489
    TmpCharB := #0;
490
 
491
  if not IsCharBracket(TmpCharA) and not IsCharBracket(TmpCharB) then exit;
492
  S := TmpCharB;
493
  if not IsCharBracket(TmpCharB) then
494
  begin
495
    P.Char := P.Char - 1;
496
    S := TmpCharA;
497
  end;
498
  Editor.GetHighlighterAttriAtRowCol(P, S, Attri);
499
 
500
  if (Editor.Highlighter.SymbolAttribute = Attri) then
501
  begin
502
    for i := low(OpenChars) to High(OpenChars) do
503
    begin
504
      if (S = OpenChars[i]) or (S = CloseChars[i]) then
505
      begin
506
        Pix := CharToPixels(P);
507
 
508
        Editor.Canvas.Brush.Style := bsSolid;//Clear;
509
        Editor.Canvas.Font.Assign(Editor.Font);
510
        Editor.Canvas.Font.Style := Attri.Style;
511
 
512
        if (TransientType = ttAfter) then
513
        begin
514
          Editor.Canvas.Font.Color := COLOR_FG;
515
          Editor.Canvas.Brush.Color := COLOR_BG;
516
        end
517
        else
518
        begin
519
          Editor.Canvas.Font.Color := Attri.Foreground;
520
          Editor.Canvas.Brush.Color := Attri.Background;
521
        end;
522
        if Editor.Canvas.Font.Color = clNone then
523
          Editor.Canvas.Font.Color := Editor.Font.Color;
524
        if Editor.Canvas.Brush.Color = clNone then
525
          Editor.Canvas.Brush.Color := Editor.Color;
526
 
527
        Editor.Canvas.TextOut(Pix.X, Pix.Y, S);
528
        P := Editor.GetMatchingBracketEx(P);
529
 
530
        if (P.Char > 0) and (P.Line > 0) then
531
        begin
532
          Pix := CharToPixels(P);
533
          if Pix.X > Editor.Gutter.Width then
534
          begin
535
            {$REGION 'Added by ViaThinkSoft'}
536
            if (TransientType = ttAfter) then
537
            begin
538
              Editor.Canvas.Font.Color := COLOR_FG;
539
              Editor.Canvas.Brush.Color := COLOR_BG;
540
            end
541
            else
542
            begin
543
              Editor.Canvas.Font.Color := Attri.Foreground;
544
              Editor.Canvas.Brush.Color := Attri.Background;
545
            end;
546
            if Editor.Canvas.Font.Color = clNone then
547
              Editor.Canvas.Font.Color := Editor.Font.Color;
548
            if Editor.Canvas.Brush.Color = clNone then
549
              Editor.Canvas.Brush.Color := Editor.Color;
550
            {$ENDREGION}
551
            if S = OpenChars[i] then
552
              Editor.Canvas.TextOut(Pix.X, Pix.Y, CloseChars[i])
553
            else Editor.Canvas.TextOut(Pix.X, Pix.Y, OpenChars[i]);
554
          end;
555
        end;
556
      end;
557
    end;
558
    Editor.Canvas.Brush.Style := bsSolid;
559
  end;
560
end;
561
 
5 daniel-mar 562
procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
563
begin
564
  SynEditFocusTimer.Enabled := false;
565
  Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
566
  SynEdit1.SetFocus;
567
end;
2 daniel-mar 568
 
23 daniel-mar 569
procedure TForm1.Timer1Timer(Sender: TObject);
570
begin
571
  {$IFDEF OnlineHelp}
572
  Timer1.Enabled := false;
573
 
574
  // TODO: Insert a small online help hint
575
  //Caption := gOnlineHelpWord;
576
  {$ENDIF}
577
end;
578
 
27 daniel-mar 579
procedure TForm1.TreeView1DblClick(Sender: TObject);
580
var
581
  tn: TTreeNode;
32 daniel-mar 582
  lineNo: integer;
27 daniel-mar 583
begin
584
  tn := TTreeView(Sender).Selected;
585
  if tn = nil then exit;
32 daniel-mar 586
  lineNo := Integer(tn.Data);
587
  if lineNo > 0 then GotoLineNo(lineNo);
27 daniel-mar 588
end;
589
 
5 daniel-mar 590
procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
27 daniel-mar 591
  const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
5 daniel-mar 592
  Headers: OleVariant; var Cancel: WordBool);
593
var
8 daniel-mar 594
  s, myURL: string;
5 daniel-mar 595
  lineno: integer;
7 daniel-mar 596
  p: integer;
5 daniel-mar 597
begin
7 daniel-mar 598
  {$REGION 'Line number references (PHP errors and warnings)'}
8 daniel-mar 599
  if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
5 daniel-mar 600
  begin
601
    try
8 daniel-mar 602
      s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
5 daniel-mar 603
      if not TryStrToInt(s, lineno) then exit;
604
      GotoLineNo(lineno);
605
      SynEditFocusTimer.Enabled := true;
606
    finally
607
      Cancel := true;
608
    end;
8 daniel-mar 609
    Exit;
5 daniel-mar 610
  end;
7 daniel-mar 611
  {$ENDREGION}
612
 
8 daniel-mar 613
  {$REGION 'Intelligent browser (executes PHP scripts)'}
7 daniel-mar 614
  if URL <> 'about:blank' then
615
  begin
616
    myUrl := URL;
617
 
8 daniel-mar 618
    p := Pos('?', myUrl);
619
    if p >= 1 then myURL := copy(myURL, 1, p-1);
7 daniel-mar 620
 
8 daniel-mar 621
    // TODO: myURL urldecode
622
    // TODO: maybe we could even open that file in the editor!
7 daniel-mar 623
 
8 daniel-mar 624
    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 625
    begin
27 daniel-mar 626
      WebBrowser1.LoadHTML(RunPHPScript(myURL), myUrl);
7 daniel-mar 627
      Cancel := true;
628
    end;
629
  end;
630
  {$ENDREGION}
5 daniel-mar 631
end;
2 daniel-mar 632
 
633
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
634
begin
13 daniel-mar 635
  FastPHPConfig.WriteInteger('User', 'FontSize', SynEdit1.Font.Size);
2 daniel-mar 636
end;
637
 
15 daniel-mar 638
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
639
var
640
  r: integer;
641
begin
642
  if SynEdit1.Modified then
643
  begin
644
    if ParamStr(1) <> '' then
645
    begin
646
      r := MessageDlg('Do you want to save?', mtConfirmation, mbYesNoCancel, 0);
647
      if r = mrCancel then
648
      begin
649
        CanClose := false;
650
        Exit;
651
      end
652
      else if r = mrYes then
653
      begin
27 daniel-mar 654
        SynEdit1.Lines.SaveToFile(GetScrapFile);
15 daniel-mar 655
        CanClose := true;
656
      end;
657
    end
658
    else
659
    begin
27 daniel-mar 660
      SynEdit1.Lines.SaveToFile(GetScrapFile);
15 daniel-mar 661
      CanClose := true;
662
    end;
663
  end;
664
end;
665
 
2 daniel-mar 666
procedure TForm1.FormCreate(Sender: TObject);
667
begin
668
  HlpPrevPageIndex := -1;
669
  CurSearchTerm := '';
13 daniel-mar 670
  Caption := Caption + ' - ' + GetScrapFile;
24 daniel-mar 671
  SrcRep := TSynEditFindReplace.Create(self);
13 daniel-mar 672
  SrcRep.Editor := SynEdit1;
29 daniel-mar 673
  SynEdit1.Gutter.Gradient := HighColorWindows;
30 daniel-mar 674
 
675
  Screen.Cursors[crMouseGutter] := LoadCursor(hInstance, 'MOUSEGUTTER');
676
  SynEdit1.Gutter.Cursor := crMouseGutter;
2 daniel-mar 677
end;
678
 
679
procedure TForm1.FormDestroy(Sender: TObject);
680
begin
681
  if Assigned(ChmIndex) then
682
  begin
683
    FreeAndNil(ChmIndex);
684
  end;
13 daniel-mar 685
  FreeAndNil(SrcRep);
27 daniel-mar 686
 
687
  if Assigned(codeExplorer) then
688
  begin
689
    codeExplorer.Terminate;
690
    codeExplorer.WaitFor;
691
    FreeAndNil(codeExplorer);
692
  end;
2 daniel-mar 693
end;
694
 
695
procedure TForm1.FormShow(Sender: TObject);
696
var
697
  ScrapFile: string;
698
begin
699
  ScrapFile := GetScrapFile;
700
  if ScrapFile = '' then
701
  begin
10 daniel-mar 702
    Application.Terminate; // Close;
2 daniel-mar 703
    exit;
704
  end;
15 daniel-mar 705
  if FileExists(ScrapFile) then
706
    SynEdit1.Lines.LoadFromFile(ScrapFile)
707
  else
708
    SynEdit1.Lines.Clear;
2 daniel-mar 709
 
710
  PageControl1.ActivePage := PlaintextTabSheet;
711
 
20 daniel-mar 712
  PageControl2.ActivePage := CodeTabsheet;
2 daniel-mar 713
  HelpTabsheet.TabVisible := false;
5 daniel-mar 714
 
13 daniel-mar 715
  SynEdit1.Font.Size := FastPHPConfig.ReadInteger('User', 'FontSize', SynEdit1.Font.Size);
5 daniel-mar 716
  SynEdit1.SetFocus;
27 daniel-mar 717
 
718
  DoubleBuffered := true;
719
  StartCodeExplorer;
2 daniel-mar 720
end;
721
 
27 daniel-mar 722
procedure TForm1.StartCodeExplorer;
723
begin
724
  codeExplorer := TRunCodeExplorer.Create(true);
725
  codeExplorer.InputRequestCallback := InputRequestCallback;
726
  codeExplorer.OutputNotifyCallback := OutputNotifyCallback;
727
  codeExplorer.PhpExe := GetPHPExe;
728
  codeExplorer.PhpFile := IncludeTrailingPathDelimiter(ExtractFileDir(Application.ExeName)) + 'codeexplorer.php'; // GetScrapFile;
729
  codeExplorer.WorkDir := ExtractFileDir(Application.ExeName);
730
  codeExplorer.Start;
731
end;
732
 
2 daniel-mar 733
function TForm1.GetScrapFile: string;
734
begin
27 daniel-mar 735
  if FScrapFile <> '' then exit(FScrapFile);
19 daniel-mar 736
 
15 daniel-mar 737
  if ParamStr(1) <> '' then
13 daniel-mar 738
    result := ParamStr(1)
739
  else
740
    result := FastPHPConfig.ReadString('Paths', 'ScrapFile', '');
2 daniel-mar 741
  if not FileExists(result) then
742
  begin
19 daniel-mar 743
    repeat
744
      if not OpenDialog3.Execute then
745
      begin
746
        Application.Terminate;
27 daniel-mar 747
        exit('');
19 daniel-mar 748
      end;
2 daniel-mar 749
 
19 daniel-mar 750
      if not DirectoryExists(ExtractFilePath(OpenDialog3.FileName)) then
751
      begin
752
        ShowMessage('Path does not exist! Please try again.');
753
      end
754
      else
755
      begin
756
        result := OpenDialog3.FileName;
757
      end;
758
    until result <> '';
2 daniel-mar 759
 
4 daniel-mar 760
    SynEdit1.Lines.Clear;
27 daniel-mar 761
    SynEdit1.Lines.SaveToFile(result);
2 daniel-mar 762
 
763
    FastPHPConfig.WriteString('Paths', 'ScrapFile', result);
19 daniel-mar 764
    FScrapFile := result;
2 daniel-mar 765
  end;
766
end;
767
 
768
procedure TForm1.Help;
769
var
19 daniel-mar 770
  IndexFile, chmFile, w, OriginalWord, url: string;
2 daniel-mar 771
  internalHtmlFile: string;
772
begin
773
  if not Assigned(ChmIndex) then
774
  begin
775
    IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
776
    IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
777
    if FileExists(IndexFile) then
778
    begin
779
      ChmIndex := TMemIniFile.Create(IndexFile);
780
    end;
781
  end;
782
 
783
  if Assigned(ChmIndex) then
784
  begin
785
    IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
786
    // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
787
 
788
    chmFile := ChangeFileExt(IndexFile, '.chm');
789
    if not FileExists(chmFile) then
790
    begin
791
      FreeAndNil(ChmIndex);
792
    end;
793
  end;
794
 
795
  if not Assigned(ChmIndex) then
796
  begin
797
    if not OpenDialog1.Execute then exit;
798
 
799
    chmFile := OpenDialog1.FileName;
800
    if not FileExists(chmFile) then exit;
801
 
802
    IndexFile := ChangeFileExt(chmFile, '.ini');
803
 
804
    if not FileExists(IndexFile) then
805
    begin
806
      Panel1.Align := alClient;
807
      Panel1.Visible := true;
808
      Panel1.BringToFront;
809
      Screen.Cursor := crHourGlass;
810
      Application.ProcessMessages;
811
      try
812
        if not ParseCHM(chmFile) then
813
        begin
814
          ShowMessage('The CHM file is not a valid PHP documentation. Cannot use help.');
815
          exit;
816
        end;
817
      finally
818
        Screen.Cursor := crDefault;
819
        Panel1.Visible := false;
820
      end;
821
 
822
      if not FileExists(IndexFile) then
823
      begin
824
        ShowMessage('Unknown error. Cannot use help.');
825
        exit;
826
      end;
827
    end;
828
 
829
    FastPHPConfig.WriteString('Paths', 'HelpIndex', IndexFile);
830
    FastPHPConfig.UpdateFile;
831
 
832
    ChmIndex := TMemIniFile.Create(IndexFile);
833
  end;
834
 
4 daniel-mar 835
  w := GetWordUnderCaret(SynEdit1);
2 daniel-mar 836
  if w = '' then exit;
8 daniel-mar 837
  if CharInSet(w[1], ['0'..'9']) then exit;
19 daniel-mar 838
 
839
  Originalword := w;
840
//  w := StringReplace(w, '_', '-', [rfReplaceAll]);
2 daniel-mar 841
  w := LowerCase(w);
842
  CurSearchTerm := w;
843
 
844
  internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, '');
845
  if internalHtmlFile = '' then
846
  begin
847
    HelpTabsheet.TabVisible := false;
848
    HlpPrevPageIndex := -1;
19 daniel-mar 849
    ShowMessageFmt('No help for "%s" available', [Originalword]);
2 daniel-mar 850
    Exit;
851
  end;
852
 
853
  url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
854
 
855
  HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
856
  HelpTabsheet.TabVisible := true;
857
  PageControl2.ActivePage := HelpTabsheet;
8 daniel-mar 858
  WebBrowser2.Navigate(url);
859
  WebBrowser2.Wait;
2 daniel-mar 860
end;
861
 
5 daniel-mar 862
procedure TForm1.GotoLineNo(LineNo:integer);
863
var
864
  line: string;
865
  i: integer;
2 daniel-mar 866
begin
5 daniel-mar 867
  SynEdit1.GotoLineAndCenter(LineNo);
868
 
869
  // Skip indent
870
  line := SynEdit1.Lines[SynEdit1.CaretY];
871
  for i := 1 to Length(line) do
872
  begin
8 daniel-mar 873
    if not CharInSet(line[i], [' ', #9]) then
5 daniel-mar 874
    begin
875
      SynEdit1.CaretX := i-1;
876
      break;
877
    end;
878
  end;
879
 
20 daniel-mar 880
  PageControl2.ActivePage := CodeTabsheet;
5 daniel-mar 881
  if SynEdit1.CanFocus then SynEdit1.SetFocus;
2 daniel-mar 882
end;
883
 
8 daniel-mar 884
procedure TForm1.PageControl2Changing(Sender: TObject;
885
  var AllowChange: Boolean);
886
begin
887
  if PageControl2.ActivePage = HelpTabsheet then
888
    HlpPrevPageIndex := -1
889
  else
890
    HlpPrevPageIndex := PageControl2.ActivePageIndex;
891
 
892
  AllowChange := true;
893
end;
894
 
5 daniel-mar 895
procedure TForm1.Memo2DblClick(Sender: TObject);
896
var
22 daniel-mar 897
  line: string;
898
 
899
  procedure _process(toFind: string);
900
  var
901
    p, lineno: integer;
902
  begin
903
    if FileSystemCaseSensitive then
904
      p := Pos(toFind, line)
905
    else
27 daniel-mar 906
      p := Pos(toFind.ToLower, line.ToLower);
22 daniel-mar 907
    if p <> 0 then
908
    begin
909
      line := copy(line, p+length(toFind), 99);
910
      if not TryStrToInt(line, lineno) then exit;
911
      GotoLineNo(lineno);
912
    end;
913
  end;
914
 
5 daniel-mar 915
begin
916
  line := memo2.Lines.Strings[Memo2.CaretPos.Y];
16 daniel-mar 917
 
18 daniel-mar 918
  {$REGION 'Possibility 1: filename.php:lineno'}
22 daniel-mar 919
  _process(ExtractFileName(GetScrapFile) + ':');
18 daniel-mar 920
  {$ENDREGION}
16 daniel-mar 921
 
18 daniel-mar 922
  {$REGION 'Possibility 2: on line xx'}
22 daniel-mar 923
  _process(ExtractFileName(GetScrapFile) + ' on line ');
18 daniel-mar 924
  {$ENDREGION}
5 daniel-mar 925
end;
926
 
17 daniel-mar 927
procedure TForm1.Memo2KeyDown(Sender: TObject; var Key: Word;
928
  Shift: TShiftState);
929
begin
930
  if ((ssCtrl in Shift) and (Key = 65)) then TMemo(Sender).SelectAll;
931
end;
932
 
5 daniel-mar 933
function TForm1.MarkUpLineReference(cont: string): string;
18 daniel-mar 934
 
935
  procedure _process(toFind: string);
22 daniel-mar 936
  var
937
    p, a, b: integer;
938
    num: integer;
939
    insert_a, insert_b: string;
5 daniel-mar 940
  begin
22 daniel-mar 941
    if FileSystemCaseSensitive then
942
      p := Pos(toFind, cont)
943
    else
27 daniel-mar 944
      p := Pos(toFind.ToLower, cont.ToLower);
18 daniel-mar 945
    while p >= 1 do
5 daniel-mar 946
    begin
22 daniel-mar 947
      a := p;
948
      b := p + length(toFind);
18 daniel-mar 949
      num := 0;
950
      while CharInSet(cont[b], ['0'..'9']) do
951
      begin
952
        num := num*10 + StrToInt(cont[b]);
953
        inc(b);
954
      end;
5 daniel-mar 955
 
18 daniel-mar 956
      insert_b := '</a>';
22 daniel-mar 957
      insert_a := '<a href="' + FASTPHP_GOTO_URI_PREFIX + IntToStr(num) + '">';
5 daniel-mar 958
 
18 daniel-mar 959
      insert(insert_b, cont, b);
960
      insert(insert_a, cont, a);
5 daniel-mar 961
 
18 daniel-mar 962
      p := b + Length(insert_a) + Length(insert_b);
5 daniel-mar 963
 
18 daniel-mar 964
      p := PosEx(toFind, cont, p+1);
965
    end;
5 daniel-mar 966
  end;
22 daniel-mar 967
 
18 daniel-mar 968
begin
969
  {$REGION 'Possibility 1: filename.php:lineno'}
22 daniel-mar 970
  _process(ExtractFileName(GetScrapFile) + ':');
18 daniel-mar 971
  {$ENDREGION}
5 daniel-mar 972
 
18 daniel-mar 973
  {$REGION 'Possibility 2: on line xx'}
22 daniel-mar 974
  _process(ExtractFileName(GetScrapFile) + ' on line ');
18 daniel-mar 975
  {$ENDREGION}
976
 
5 daniel-mar 977
  result := cont;
978
end;
979
 
31 daniel-mar 980
function TForm1.InputRequestCallback: AnsiString;
27 daniel-mar 981
begin
31 daniel-mar 982
  result := UTF8Encode(SynEdit1.Text);
27 daniel-mar 983
end;
984
 
31 daniel-mar 985
procedure TForm1.OutputNotifyCallback(const data: AnsiString);
27 daniel-mar 986
begin
987
  TreeView1.FillWithFastPHPData(data);
988
end;
989
 
2 daniel-mar 990
end.