unit FormCli; { FormCli - Remote forms client engine for Delphi 1.0. } { } { Receives form/control commands from a server via a transport interface, } { creates native Windows 3.1 controls, and sends user events back. } { } { Transport is abstracted: TFormTransport defines ReadMessage/WriteMessage. } { The caller provides a concrete descendant (e.g., serial or string list). } { } { Call ProcessMessages from the main loop to pump incoming commands. } interface uses SysUtils, Classes, Controls, Forms, StdCtrls, ExtCtrls, MPlayer, WinTypes, WinProcs; const MaxMsgLen = 4096; type { Transport interface - override in descendant } TFormTransport = class(TObject) public function ReadMessage(Buf: PChar; BufSize: Integer): Integer; virtual; abstract; procedure WriteMessage(Buf: PChar; Len: Integer); virtual; abstract; end; TCtrlTypeE = (ctUnknown, ctLabel, ctEdit, ctButton, ctCheckBox, ctListBox, ctComboBox, ctMemo, ctImage, ctGroupBox, ctRadioButton, ctPanel, ctScrollBar, ctMediaPlayer); { Bound event flags } TBoundEvent = (beDblClick, beKeyDown, beKeyUp, beEnter, beExit, beMouseDown, beMouseUp, beMouseMove, beClick, beNotify); TBoundEvents = set of TBoundEvent; { Per-control record } PFormCtrlRec = ^TFormCtrlRec; TFormCtrlRec = record CtrlId: Integer; CtrlType: TCtrlTypeE; Control: TControl; Bound: TBoundEvents; end; { Per-form record } PFormRec = ^TFormRec; TFormRec = record FormId: Integer; Form: TForm; Ctrls: TList; { of PFormCtrlRec } end; { Client engine } TFormClient = class(TObject) private FTransport: TFormTransport; FForms: TList; { of PFormRec } FMsgBuf: PChar; { read buffer } FTmpBuf: PChar; { scratch buffer for outgoing messages } FBasePath: string; { Command dispatch } procedure DoCtrlCreate(P: PChar); procedure DoCtrlSet(P: PChar); procedure DoEventBind(P: PChar); procedure DoEventUnbind(P: PChar); procedure DoFormCreate(P: PChar); procedure DoFormDestroy(P: PChar); procedure DoFormHide(P: PChar); procedure DoFormShow(P: PChar); procedure DispatchCommand(Buf: PChar); { Form/control lookup } function FindForm(FormId: Integer): PFormRec; function FindCtrl(FR: PFormRec; CtrlId: Integer): PFormCtrlRec; procedure FreeFormRec(FR: PFormRec); procedure FreeCtrlRec(CR: PFormCtrlRec); { Property application } procedure ApplyProp(CR: PFormCtrlRec; Key, Value: PChar); procedure ApplyInlineProps(CR: PFormCtrlRec; P: PChar); { Event wiring } procedure WireAutoEvents(CR: PFormCtrlRec); procedure WireOptEvent(CR: PFormCtrlRec; const EventName: string); procedure UnwireOptEvent(CR: PFormCtrlRec; const EventName: string); { Event handlers } procedure HandleButtonClick(Sender: TObject); procedure HandleCheckBoxClick(Sender: TObject); procedure HandleComboBoxChange(Sender: TObject); procedure HandleComboBoxSelect(Sender: TObject); procedure HandleDblClick(Sender: TObject); procedure HandleEditChange(Sender: TObject); procedure HandleEnter(Sender: TObject); procedure HandleExit(Sender: TObject); procedure HandleFormClose(Sender: TObject; var Action: TCloseAction); procedure HandleKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure HandleKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure HandleListBoxSelect(Sender: TObject); procedure HandleMemoChange(Sender: TObject); procedure HandleMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure HandleMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); procedure HandleMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure HandleClick(Sender: TObject); procedure HandleRadioButtonClick(Sender: TObject); procedure HandleScrollBarChange(Sender: TObject); procedure HandleMediaPlayerNotify(Sender: TObject); { Outgoing event helpers } procedure SendEvent(FormId, CtrlId: Integer; const EventName: string; const Data: string); { String parsing } function ParseToken(var P: PChar; Buf: PChar; BufSize: Integer): Boolean; function ParseInt(var P: PChar): Integer; procedure UnescapeString(Src, Dst: PChar; DstSize: Integer); procedure EscapeString(Src, Dst: PChar; DstSize: Integer); public constructor Create(ATransport: TFormTransport); destructor Destroy; override; procedure ProcessMessages; property Transport: TFormTransport read FTransport; property BasePath: string read FBasePath write FBasePath; end; implementation { ----- String parsing helpers --------------------------------------------- } function TFormClient.ParseToken(var P: PChar; Buf: PChar; BufSize: Integer): Boolean; var I: Integer; C: Char; begin Result := False; { Skip whitespace } while (P^ = ' ') or (P^ = #9) do Inc(P); if P^ = #0 then Exit; I := 0; if P^ = '"' then begin { Quoted string } Inc(P); while (P^ <> #0) and (P^ <> '"') do begin if (P^ = '\') and ((P + 1)^ <> #0) then begin Inc(P); C := P^; case C of 'n': C := #10; 'r': C := #13; 't': C := #9; '"': C := '"'; '\': C := '\'; end; if I < BufSize - 1 then begin Buf[I] := C; Inc(I); end; end else begin if I < BufSize - 1 then begin Buf[I] := P^; Inc(I); end; end; Inc(P); end; if P^ = '"' then Inc(P); end else begin { Bare token } while (P^ <> #0) and (P^ <> ' ') and (P^ <> #9) do begin if I < BufSize - 1 then begin Buf[I] := P^; Inc(I); end; Inc(P); end; end; Buf[I] := #0; Result := I > 0; end; function TFormClient.ParseInt(var P: PChar): Integer; var Tok: array[0..31] of Char; begin if ParseToken(P, Tok, SizeOf(Tok)) then Result := StrToIntDef(StrPas(Tok), 0) else Result := 0; end; procedure TFormClient.UnescapeString(Src, Dst: PChar; DstSize: Integer); var I: Integer; C: Char; begin I := 0; while (Src^ <> #0) and (I < DstSize - 1) do begin if (Src^ = '\') and ((Src + 1)^ <> #0) then begin Inc(Src); C := Src^; case C of 'n': C := #10; 'r': C := #13; 't': C := #9; '"': C := '"'; '\': C := '\'; end; Dst[I] := C; Inc(I); end else begin Dst[I] := Src^; Inc(I); end; Inc(Src); end; Dst[I] := #0; end; procedure TFormClient.EscapeString(Src, Dst: PChar; DstSize: Integer); var I: Integer; begin I := 0; while (Src^ <> #0) and (I < DstSize - 2) do begin case Src^ of '"': begin if I + 2 > DstSize - 1 then Break; Dst[I] := '\'; Inc(I); Dst[I] := '"'; Inc(I); end; '\': begin if I + 2 > DstSize - 1 then Break; Dst[I] := '\'; Inc(I); Dst[I] := '\'; Inc(I); end; #10: begin if I + 2 > DstSize - 1 then Break; Dst[I] := '\'; Inc(I); Dst[I] := 'n'; Inc(I); end; #13: begin if I + 2 > DstSize - 1 then Break; Dst[I] := '\'; Inc(I); Dst[I] := 'r'; Inc(I); end; #9: begin if I + 2 > DstSize - 1 then Break; Dst[I] := '\'; Inc(I); Dst[I] := 't'; Inc(I); end; else Dst[I] := Src^; Inc(I); end; Inc(Src); end; Dst[I] := #0; end; { ----- Form/control lookup ------------------------------------------------ } function TFormClient.FindForm(FormId: Integer): PFormRec; var I: Integer; FR: PFormRec; begin Result := nil; for I := 0 to FForms.Count - 1 do begin FR := PFormRec(FForms[I]); if FR^.FormId = FormId then begin Result := FR; Exit; end; end; end; function TFormClient.FindCtrl(FR: PFormRec; CtrlId: Integer): PFormCtrlRec; var I: Integer; CR: PFormCtrlRec; begin Result := nil; for I := 0 to FR^.Ctrls.Count - 1 do begin CR := PFormCtrlRec(FR^.Ctrls[I]); if CR^.CtrlId = CtrlId then begin Result := CR; Exit; end; end; end; procedure TFormClient.FreeCtrlRec(CR: PFormCtrlRec); begin if CR^.Control <> nil then CR^.Control.Free; Dispose(CR); end; procedure TFormClient.FreeFormRec(FR: PFormRec); var I: Integer; begin for I := 0 to FR^.Ctrls.Count - 1 do FreeCtrlRec(PFormCtrlRec(FR^.Ctrls[I])); FR^.Ctrls.Free; if FR^.Form <> nil then FR^.Form.Free; Dispose(FR); end; { ----- Constructor / Destructor ------------------------------------------- } constructor TFormClient.Create(ATransport: TFormTransport); begin inherited Create; FTransport := ATransport; FForms := TList.Create; GetMem(FMsgBuf, MaxMsgLen); GetMem(FTmpBuf, MaxMsgLen); end; destructor TFormClient.Destroy; var I: Integer; begin for I := 0 to FForms.Count - 1 do FreeFormRec(PFormRec(FForms[I])); FForms.Free; FreeMem(FMsgBuf, MaxMsgLen); FreeMem(FTmpBuf, MaxMsgLen); inherited Destroy; end; { ----- ID encoding in Tag ------------------------------------------------ } { Tag := (FormId shl 16) or CtrlId } { ----- Event handlers ----------------------------------------------------- } procedure TFormClient.SendEvent(FormId, CtrlId: Integer; const EventName: string; const Data: string); var Msg: string; begin if Data = '' then Msg := 'EVENT ' + IntToStr(FormId) + ' ' + IntToStr(CtrlId) + ' ' + EventName else Msg := 'EVENT ' + IntToStr(FormId) + ' ' + IntToStr(CtrlId) + ' ' + EventName + ' ' + Data; StrPCopy(FTmpBuf, Msg); FTransport.WriteMessage(FTmpBuf, Length(Msg)); end; procedure TFormClient.HandleButtonClick(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'Click', ''); end; procedure TFormClient.HandleCheckBoxClick(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'Click', ''); end; procedure TFormClient.HandleEditChange(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; Escaped: array[0..4095] of Char; Txt: string; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; Txt := (Sender as TEdit).Text; StrPCopy(FTmpBuf, Txt); EscapeString(FTmpBuf, Escaped, SizeOf(Escaped)); SendEvent(FormId, CtrlId, 'Change', '"' + StrPas(Escaped) + '"'); end; procedure TFormClient.HandleMemoChange(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; Escaped: array[0..4095] of Char; Txt: string; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; Txt := (Sender as TMemo).Text; StrPCopy(FTmpBuf, Txt); EscapeString(FTmpBuf, Escaped, SizeOf(Escaped)); SendEvent(FormId, CtrlId, 'Change', '"' + StrPas(Escaped) + '"'); end; procedure TFormClient.HandleComboBoxChange(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; Escaped: array[0..4095] of Char; Txt: string; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; Txt := (Sender as TComboBox).Text; StrPCopy(FTmpBuf, Txt); EscapeString(FTmpBuf, Escaped, SizeOf(Escaped)); SendEvent(FormId, CtrlId, 'Change', '"' + StrPas(Escaped) + '"'); end; procedure TFormClient.HandleListBoxSelect(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; Idx: Integer; Escaped: array[0..4095] of Char; Txt: string; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; Idx := (Sender as TListBox).ItemIndex; if Idx >= 0 then Txt := (Sender as TListBox).Items[Idx] else Txt := ''; StrPCopy(FTmpBuf, Txt); EscapeString(FTmpBuf, Escaped, SizeOf(Escaped)); SendEvent(FormId, CtrlId, 'Select', IntToStr(Idx) + ' "' + StrPas(Escaped) + '"'); end; procedure TFormClient.HandleComboBoxSelect(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; Idx: Integer; Escaped: array[0..4095] of Char; Txt: string; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; Idx := (Sender as TComboBox).ItemIndex; if Idx >= 0 then Txt := (Sender as TComboBox).Items[Idx] else Txt := ''; StrPCopy(FTmpBuf, Txt); EscapeString(FTmpBuf, Escaped, SizeOf(Escaped)); SendEvent(FormId, CtrlId, 'Select', IntToStr(Idx) + ' "' + StrPas(Escaped) + '"'); end; procedure TFormClient.HandleDblClick(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'DblClick', ''); end; procedure TFormClient.HandleEnter(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'Enter', ''); end; procedure TFormClient.HandleExit(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'Exit', ''); end; procedure TFormClient.HandleKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'KeyDown', IntToStr(Key)); end; procedure TFormClient.HandleKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'KeyUp', IntToStr(Key)); end; procedure TFormClient.HandleMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var Tag: Longint; FormId: Integer; CtrlId: Integer; Btn: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; Btn := Ord(Button); SendEvent(FormId, CtrlId, 'MouseDown', IntToStr(X) + ' ' + IntToStr(Y) + ' ' + IntToStr(Btn)); end; procedure TFormClient.HandleMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var Tag: Longint; FormId: Integer; CtrlId: Integer; Btn: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; Btn := Ord(Button); SendEvent(FormId, CtrlId, 'MouseUp', IntToStr(X) + ' ' + IntToStr(Y) + ' ' + IntToStr(Btn)); end; procedure TFormClient.HandleMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'MouseMove', IntToStr(X) + ' ' + IntToStr(Y) + ' 0'); end; procedure TFormClient.HandleClick(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'Click', ''); end; procedure TFormClient.HandleRadioButtonClick(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'Click', ''); end; procedure TFormClient.HandleScrollBarChange(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'Change', IntToStr((Sender as TScrollBar).Position)); end; procedure TFormClient.HandleMediaPlayerNotify(Sender: TObject); var Tag: Longint; FormId: Integer; CtrlId: Integer; begin Tag := (Sender as TControl).Tag; FormId := Tag shr 16; CtrlId := Tag and $FFFF; SendEvent(FormId, CtrlId, 'Notify', ''); end; procedure TFormClient.HandleFormClose(Sender: TObject; var Action: TCloseAction); var Tag: Longint; FormId: Integer; begin Tag := (Sender as TForm).Tag; FormId := Tag shr 16; SendEvent(FormId, 0, 'Close', ''); Action := caNone; { Server decides whether to destroy } end; { ----- Event wiring ------------------------------------------------------- } procedure TFormClient.WireAutoEvents(CR: PFormCtrlRec); begin case CR^.CtrlType of ctButton: (CR^.Control as TButton).OnClick := HandleButtonClick; ctCheckBox: (CR^.Control as TCheckBox).OnClick := HandleCheckBoxClick; ctEdit: (CR^.Control as TEdit).OnChange := HandleEditChange; ctMemo: (CR^.Control as TMemo).OnChange := HandleMemoChange; ctListBox: (CR^.Control as TListBox).OnClick := HandleListBoxSelect; ctComboBox: begin (CR^.Control as TComboBox).OnClick := HandleComboBoxSelect; (CR^.Control as TComboBox).OnChange := HandleComboBoxChange; end; ctRadioButton: (CR^.Control as TRadioButton).OnClick := HandleRadioButtonClick; ctScrollBar: (CR^.Control as TScrollBar).OnChange := HandleScrollBarChange; end; end; procedure TFormClient.WireOptEvent(CR: PFormCtrlRec; const EventName: string); begin if EventName = 'DblClick' then begin (CR^.Control as TControl).OnDblClick := HandleDblClick; CR^.Bound := CR^.Bound + [beDblClick]; end else if EventName = 'Enter' then begin if CR^.Control is TWinControl then (CR^.Control as TWinControl).OnEnter := HandleEnter; CR^.Bound := CR^.Bound + [beEnter]; end else if EventName = 'Exit' then begin if CR^.Control is TWinControl then (CR^.Control as TWinControl).OnExit := HandleExit; CR^.Bound := CR^.Bound + [beExit]; end else if EventName = 'KeyDown' then begin if CR^.Control is TWinControl then (CR^.Control as TWinControl).OnKeyDown := HandleKeyDown; CR^.Bound := CR^.Bound + [beKeyDown]; end else if EventName = 'KeyUp' then begin if CR^.Control is TWinControl then (CR^.Control as TWinControl).OnKeyUp := HandleKeyUp; CR^.Bound := CR^.Bound + [beKeyUp]; end else if EventName = 'MouseDown' then begin (CR^.Control as TControl).OnMouseDown := HandleMouseDown; CR^.Bound := CR^.Bound + [beMouseDown]; end else if EventName = 'MouseUp' then begin (CR^.Control as TControl).OnMouseUp := HandleMouseUp; CR^.Bound := CR^.Bound + [beMouseUp]; end else if EventName = 'MouseMove' then begin (CR^.Control as TControl).OnMouseMove := HandleMouseMove; CR^.Bound := CR^.Bound + [beMouseMove]; end else if EventName = 'Click' then begin if CR^.Control is TImage then (CR^.Control as TImage).OnClick := HandleClick else if CR^.Control is TPanel then (CR^.Control as TPanel).OnClick := HandleClick else if CR^.Control is TGroupBox then (CR^.Control as TGroupBox).OnClick := HandleClick; CR^.Bound := CR^.Bound + [beClick]; end else if EventName = 'Notify' then begin if CR^.Control is TMediaPlayer then (CR^.Control as TMediaPlayer).OnNotify := HandleMediaPlayerNotify; CR^.Bound := CR^.Bound + [beNotify]; end; end; procedure TFormClient.UnwireOptEvent(CR: PFormCtrlRec; const EventName: string); begin if EventName = 'DblClick' then begin (CR^.Control as TControl).OnDblClick := nil; CR^.Bound := CR^.Bound - [beDblClick]; end else if EventName = 'Enter' then begin if CR^.Control is TWinControl then (CR^.Control as TWinControl).OnEnter := nil; CR^.Bound := CR^.Bound - [beEnter]; end else if EventName = 'Exit' then begin if CR^.Control is TWinControl then (CR^.Control as TWinControl).OnExit := nil; CR^.Bound := CR^.Bound - [beExit]; end else if EventName = 'KeyDown' then begin if CR^.Control is TWinControl then (CR^.Control as TWinControl).OnKeyDown := nil; CR^.Bound := CR^.Bound - [beKeyDown]; end else if EventName = 'KeyUp' then begin if CR^.Control is TWinControl then (CR^.Control as TWinControl).OnKeyUp := nil; CR^.Bound := CR^.Bound - [beKeyUp]; end else if EventName = 'MouseDown' then begin (CR^.Control as TControl).OnMouseDown := nil; CR^.Bound := CR^.Bound - [beMouseDown]; end else if EventName = 'MouseUp' then begin (CR^.Control as TControl).OnMouseUp := nil; CR^.Bound := CR^.Bound - [beMouseUp]; end else if EventName = 'MouseMove' then begin (CR^.Control as TControl).OnMouseMove := nil; CR^.Bound := CR^.Bound - [beMouseMove]; end else if EventName = 'Click' then begin if CR^.Control is TImage then (CR^.Control as TImage).OnClick := nil else if CR^.Control is TPanel then (CR^.Control as TPanel).OnClick := nil else if CR^.Control is TGroupBox then (CR^.Control as TGroupBox).OnClick := nil; CR^.Bound := CR^.Bound - [beClick]; end else if EventName = 'Notify' then begin if CR^.Control is TMediaPlayer then (CR^.Control as TMediaPlayer).OnNotify := nil; CR^.Bound := CR^.Bound - [beNotify]; end; end; { ----- Property application ----------------------------------------------- } procedure TFormClient.ApplyProp(CR: PFormCtrlRec; Key, Value: PChar); var S: string; Unesc: array[0..4095] of Char; N: Integer; Lines: TStringList; P: PChar; Start: PChar; begin S := StrPas(Key); if S = 'Caption' then begin UnescapeString(Value, Unesc, SizeOf(Unesc)); case CR^.CtrlType of ctLabel: (CR^.Control as TLabel).Caption := StrPas(Unesc); ctButton: (CR^.Control as TButton).Caption := StrPas(Unesc); ctCheckBox: (CR^.Control as TCheckBox).Caption := StrPas(Unesc); ctGroupBox: (CR^.Control as TGroupBox).Caption := StrPas(Unesc); ctRadioButton: (CR^.Control as TRadioButton).Caption := StrPas(Unesc); ctPanel: (CR^.Control as TPanel).Caption := StrPas(Unesc); end; end else if S = 'Text' then begin UnescapeString(Value, Unesc, SizeOf(Unesc)); case CR^.CtrlType of ctEdit: (CR^.Control as TEdit).Text := StrPas(Unesc); ctComboBox: (CR^.Control as TComboBox).Text := StrPas(Unesc); ctMemo: begin { Convert \n to Lines } (CR^.Control as TMemo).Lines.Clear; Lines := TStringList.Create; try P := Unesc; Start := P; while P^ <> #0 do begin if P^ = #10 then begin P^ := #0; Lines.Add(StrPas(Start)); Inc(P); Start := P; end else if (P^ = #13) and ((P + 1)^ = #10) then begin P^ := #0; Lines.Add(StrPas(Start)); Inc(P, 2); Start := P; end else Inc(P); end; if Start^ <> #0 then Lines.Add(StrPas(Start)); (CR^.Control as TMemo).Lines.Assign(Lines); finally Lines.Free; end; end; end; end else if S = 'Items' then begin UnescapeString(Value, Unesc, SizeOf(Unesc)); case CR^.CtrlType of ctListBox: begin (CR^.Control as TListBox).Items.Clear; P := Unesc; Start := P; while P^ <> #0 do begin if P^ = #10 then begin P^ := #0; (CR^.Control as TListBox).Items.Add(StrPas(Start)); Inc(P); Start := P; end else Inc(P); end; if Start^ <> #0 then (CR^.Control as TListBox).Items.Add(StrPas(Start)); end; ctComboBox: begin (CR^.Control as TComboBox).Items.Clear; P := Unesc; Start := P; while P^ <> #0 do begin if P^ = #10 then begin P^ := #0; (CR^.Control as TComboBox).Items.Add(StrPas(Start)); Inc(P); Start := P; end else Inc(P); end; if Start^ <> #0 then (CR^.Control as TComboBox).Items.Add(StrPas(Start)); end; end; end else if S = 'Checked' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctCheckBox then (CR^.Control as TCheckBox).Checked := (N <> 0) else if CR^.CtrlType = ctRadioButton then (CR^.Control as TRadioButton).Checked := (N <> 0); end else if S = 'Enabled' then begin N := StrToIntDef(StrPas(Value), 1); CR^.Control.Enabled := (N <> 0); end else if S = 'Visible' then begin N := StrToIntDef(StrPas(Value), 1); CR^.Control.Visible := (N <> 0); end else if S = 'MaxLength' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctEdit then (CR^.Control as TEdit).MaxLength := N; end else if S = 'ReadOnly' then begin N := StrToIntDef(StrPas(Value), 0); case CR^.CtrlType of ctEdit: (CR^.Control as TEdit).ReadOnly := (N <> 0); ctMemo: (CR^.Control as TMemo).ReadOnly := (N <> 0); end; end else if S = 'ScrollBars' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctMemo then (CR^.Control as TMemo).ScrollBars := TScrollStyle(N); end else if S = 'TabOrder' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.Control is TWinControl then (CR^.Control as TWinControl).TabOrder := N; end else if S = 'ItemIndex' then begin N := StrToIntDef(StrPas(Value), -1); case CR^.CtrlType of ctListBox: (CR^.Control as TListBox).ItemIndex := N; ctComboBox: (CR^.Control as TComboBox).ItemIndex := N; end; end else if S = 'Stretch' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctImage then (CR^.Control as TImage).Stretch := (N <> 0); end else if S = 'Center' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctImage then (CR^.Control as TImage).Center := (N <> 0); end else if S = 'Transparent' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctImage then (CR^.Control as TImage).Transparent := (N <> 0); end else if S = 'Picture' then begin if CR^.CtrlType = ctImage then begin UnescapeString(Value, Unesc, SizeOf(Unesc)); try if FBasePath <> '' then (CR^.Control as TImage).Picture.LoadFromFile(FBasePath + '\' + StrPas(Unesc)) else (CR^.Control as TImage).Picture.LoadFromFile(StrPas(Unesc)); except end; end; end else if S = 'BevelOuter' then begin N := StrToIntDef(StrPas(Value), 1); if CR^.CtrlType = ctPanel then (CR^.Control as TPanel).BevelOuter := TBevelCut(N); end else if S = 'BevelInner' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctPanel then (CR^.Control as TPanel).BevelInner := TBevelCut(N); end else if S = 'BorderStyle' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctPanel then (CR^.Control as TPanel).BorderStyle := TBorderStyle(N); end else if S = 'Kind' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctScrollBar then (CR^.Control as TScrollBar).Kind := TScrollBarKind(N); end else if S = 'Min' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctScrollBar then (CR^.Control as TScrollBar).Min := N; end else if S = 'Max' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctScrollBar then (CR^.Control as TScrollBar).Max := N; end else if S = 'Position' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctScrollBar then (CR^.Control as TScrollBar).Position := N; end else if S = 'LargeChange' then begin N := StrToIntDef(StrPas(Value), 1); if CR^.CtrlType = ctScrollBar then (CR^.Control as TScrollBar).LargeChange := N; end else if S = 'SmallChange' then begin N := StrToIntDef(StrPas(Value), 1); if CR^.CtrlType = ctScrollBar then (CR^.Control as TScrollBar).SmallChange := N; end else if S = 'FileName' then begin if CR^.CtrlType = ctMediaPlayer then begin UnescapeString(Value, Unesc, SizeOf(Unesc)); if FBasePath <> '' then (CR^.Control as TMediaPlayer).FileName := FBasePath + '\' + StrPas(Unesc) else (CR^.Control as TMediaPlayer).FileName := StrPas(Unesc); end; end else if S = 'DeviceType' then begin if CR^.CtrlType = ctMediaPlayer then begin UnescapeString(Value, Unesc, SizeOf(Unesc)); S := StrPas(Unesc); if S = 'dtAutoSelect' then (CR^.Control as TMediaPlayer).DeviceType := dtAutoSelect else if S = 'dtAVIVideo' then (CR^.Control as TMediaPlayer).DeviceType := dtAVIVideo else if S = 'dtCDAudio' then (CR^.Control as TMediaPlayer).DeviceType := dtCDAudio else if S = 'dtDAT' then (CR^.Control as TMediaPlayer).DeviceType := dtDAT else if S = 'dtDigitalVideo' then (CR^.Control as TMediaPlayer).DeviceType := dtDigitalVideo else if S = 'dtMMMovie' then (CR^.Control as TMediaPlayer).DeviceType := dtMMMovie else if S = 'dtOther' then (CR^.Control as TMediaPlayer).DeviceType := dtOther else if S = 'dtOverlay' then (CR^.Control as TMediaPlayer).DeviceType := dtOverlay else if S = 'dtScanner' then (CR^.Control as TMediaPlayer).DeviceType := dtScanner else if S = 'dtSequencer' then (CR^.Control as TMediaPlayer).DeviceType := dtSequencer else if S = 'dtVCR' then (CR^.Control as TMediaPlayer).DeviceType := dtVCR else if S = 'dtVideodisc' then (CR^.Control as TMediaPlayer).DeviceType := dtVideodisc else if S = 'dtWaveAudio' then (CR^.Control as TMediaPlayer).DeviceType := dtWaveAudio else (CR^.Control as TMediaPlayer).DeviceType := dtAutoSelect; end; end else if S = 'AutoOpen' then begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctMediaPlayer then (CR^.Control as TMediaPlayer).AutoOpen := (N <> 0); end else if S = 'Command' then begin if CR^.CtrlType = ctMediaPlayer then begin UnescapeString(Value, Unesc, SizeOf(Unesc)); S := StrPas(Unesc); try if S = 'Open' then (CR^.Control as TMediaPlayer).Open else if S = 'Play' then (CR^.Control as TMediaPlayer).Play else if S = 'Stop' then (CR^.Control as TMediaPlayer).Stop else if S = 'Close' then (CR^.Control as TMediaPlayer).Close else if S = 'Pause' then (CR^.Control as TMediaPlayer).Pause else if S = 'Resume' then (CR^.Control as TMediaPlayer).Resume else if S = 'Rewind' then (CR^.Control as TMediaPlayer).Rewind else if S = 'Next' then (CR^.Control as TMediaPlayer).Next else if S = 'Previous' then (CR^.Control as TMediaPlayer).Previous; except end; end; end; end; procedure TFormClient.ApplyInlineProps(CR: PFormCtrlRec; P: PChar); var Token: array[0..4095] of Char; Key: array[0..63] of Char; Value: array[0..4095] of Char; Eq: PChar; begin { Parse Key="value" or Key=number pairs } while ParseToken(P, Token, SizeOf(Token)) do begin { Find '=' in token } Eq := StrScan(Token, '='); if Eq = nil then Continue; { Split at '=' } Eq^ := #0; StrCopy(Key, Token); { Value might be quoted (already unquoted by ParseToken if entire token was quoted). But here Key=Value is a single bare token, so the value part after '=' needs special handling. } { Back up P to re-parse the value part properly } { Actually, the token was read as "Key=value" or "Key="value"" } { For Key="value", the quote is part of the remaining stream. } { Let's handle both cases: } Inc(Eq); if Eq^ = '"' then begin { Value starts with quote - was not consumed by ParseToken since the whole thing was read as bare token up to space. Actually ParseToken reads bare tokens up to space, so Key="val" with no spaces reads as one token: Key="val". Strip quotes manually. } Inc(Eq); StrCopy(Value, Eq); { Strip trailing quote } if StrLen(Value) > 0 then begin if Value[StrLen(Value) - 1] = '"' then Value[StrLen(Value) - 1] := #0; end; end else begin StrCopy(Value, Eq); end; ApplyProp(CR, Key, Value); end; end; { ----- Control type mapping ----------------------------------------------- } function MapTypeName(Name: PChar): TCtrlTypeE; var S: string; begin S := StrPas(Name); if S = 'Label' then Result := ctLabel else if S = 'Edit' then Result := ctEdit else if S = 'Button' then Result := ctButton else if S = 'CheckBox' then Result := ctCheckBox else if S = 'ListBox' then Result := ctListBox else if S = 'ComboBox' then Result := ctComboBox else if S = 'Memo' then Result := ctMemo else if S = 'Image' then Result := ctImage else if S = 'GroupBox' then Result := ctGroupBox else if S = 'RadioButton' then Result := ctRadioButton else if S = 'Panel' then Result := ctPanel else if S = 'ScrollBar' then Result := ctScrollBar else if S = 'MediaPlayer' then Result := ctMediaPlayer else Result := ctUnknown; end; { ----- Command handlers --------------------------------------------------- } procedure TFormClient.DoFormCreate(P: PChar); var FR: PFormRec; FormId: Integer; W: Integer; H: Integer; Title: array[0..255] of Char; begin FormId := ParseInt(P); W := ParseInt(P); H := ParseInt(P); if not ParseToken(P, Title, SizeOf(Title)) then Title[0] := #0; New(FR); FR^.FormId := FormId; FR^.Form := TForm.CreateNew(Application); FR^.Ctrls := TList.Create; FR^.Form.Caption := StrPas(Title); FR^.Form.ClientWidth := W; FR^.Form.ClientHeight := H; FR^.Form.Position := poScreenCenter; FR^.Form.Tag := Longint(FormId) shl 16; FR^.Form.OnClose := HandleFormClose; FForms.Add(FR); end; procedure TFormClient.DoFormShow(P: PChar); var FormId: Integer; FR: PFormRec; begin FormId := ParseInt(P); FR := FindForm(FormId); if FR <> nil then FR^.Form.Show; end; procedure TFormClient.DoFormHide(P: PChar); var FormId: Integer; FR: PFormRec; begin FormId := ParseInt(P); FR := FindForm(FormId); if FR <> nil then FR^.Form.Hide; end; procedure TFormClient.DoFormDestroy(P: PChar); var FormId: Integer; FR: PFormRec; Idx: Integer; begin FormId := ParseInt(P); FR := FindForm(FormId); if FR = nil then Exit; Idx := FForms.IndexOf(FR); if Idx >= 0 then FForms.Delete(Idx); FreeFormRec(FR); end; procedure TFormClient.DoCtrlCreate(P: PChar); var FormId: Integer; CtrlId: Integer; TypeName: array[0..31] of Char; Left: Integer; Top: Integer; Width: Integer; Height: Integer; FR: PFormRec; CR: PFormCtrlRec; CType: TCtrlTypeE; Ctrl: TControl; begin FormId := ParseInt(P); CtrlId := ParseInt(P); if not ParseToken(P, TypeName, SizeOf(TypeName)) then Exit; Left := ParseInt(P); Top := ParseInt(P); Width := ParseInt(P); Height := ParseInt(P); FR := FindForm(FormId); if FR = nil then Exit; CType := MapTypeName(TypeName); if CType = ctUnknown then Exit; { Create the control } Ctrl := nil; case CType of ctLabel: begin Ctrl := TLabel.Create(FR^.Form); (Ctrl as TLabel).AutoSize := False; end; ctEdit: Ctrl := TEdit.Create(FR^.Form); ctButton: Ctrl := TButton.Create(FR^.Form); ctCheckBox: Ctrl := TCheckBox.Create(FR^.Form); ctListBox: Ctrl := TListBox.Create(FR^.Form); ctComboBox: Ctrl := TComboBox.Create(FR^.Form); ctMemo: Ctrl := TMemo.Create(FR^.Form); ctImage: begin Ctrl := TImage.Create(FR^.Form); (Ctrl as TImage).AutoSize := False; end; ctGroupBox: Ctrl := TGroupBox.Create(FR^.Form); ctRadioButton: Ctrl := TRadioButton.Create(FR^.Form); ctPanel: Ctrl := TPanel.Create(FR^.Form); ctScrollBar: Ctrl := TScrollBar.Create(FR^.Form); ctMediaPlayer: Ctrl := TMediaPlayer.Create(FR^.Form); end; if Ctrl = nil then Exit; { Set parent and geometry } if Ctrl is TWinControl then (Ctrl as TWinControl).Parent := FR^.Form else Ctrl.Parent := FR^.Form; Ctrl.Left := Left; Ctrl.Top := Top; Ctrl.Width := Width; Ctrl.Height := Height; Ctrl.Tag := (Longint(FormId) shl 16) or CtrlId; { Create control record } New(CR); CR^.CtrlId := CtrlId; CR^.CtrlType := CType; CR^.Control := Ctrl; CR^.Bound := []; FR^.Ctrls.Add(CR); { Wire auto events } WireAutoEvents(CR); { Apply inline properties } ApplyInlineProps(CR, P); end; procedure TFormClient.DoCtrlSet(P: PChar); var FormId: Integer; CtrlId: Integer; FR: PFormRec; CR: PFormCtrlRec; begin FormId := ParseInt(P); CtrlId := ParseInt(P); FR := FindForm(FormId); if FR = nil then Exit; CR := FindCtrl(FR, CtrlId); if CR = nil then Exit; ApplyInlineProps(CR, P); end; procedure TFormClient.DoEventBind(P: PChar); var FormId: Integer; CtrlId: Integer; EventName: array[0..63] of Char; FR: PFormRec; CR: PFormCtrlRec; begin FormId := ParseInt(P); CtrlId := ParseInt(P); if not ParseToken(P, EventName, SizeOf(EventName)) then Exit; FR := FindForm(FormId); if FR = nil then Exit; CR := FindCtrl(FR, CtrlId); if CR = nil then Exit; WireOptEvent(CR, StrPas(EventName)); end; procedure TFormClient.DoEventUnbind(P: PChar); var FormId: Integer; CtrlId: Integer; EventName: array[0..63] of Char; FR: PFormRec; CR: PFormCtrlRec; begin FormId := ParseInt(P); CtrlId := ParseInt(P); if not ParseToken(P, EventName, SizeOf(EventName)) then Exit; FR := FindForm(FormId); if FR = nil then Exit; CR := FindCtrl(FR, CtrlId); if CR = nil then Exit; UnwireOptEvent(CR, StrPas(EventName)); end; { ----- Command dispatch --------------------------------------------------- } procedure TFormClient.DispatchCommand(Buf: PChar); var P: PChar; Cmd: array[0..31] of Char; begin P := Buf; if not ParseToken(P, Cmd, SizeOf(Cmd)) then Exit; if StrComp(Cmd, 'FORM.CREATE') = 0 then DoFormCreate(P) else if StrComp(Cmd, 'FORM.SHOW') = 0 then DoFormShow(P) else if StrComp(Cmd, 'FORM.HIDE') = 0 then DoFormHide(P) else if StrComp(Cmd, 'FORM.DESTROY') = 0 then DoFormDestroy(P) else if StrComp(Cmd, 'CTRL.CREATE') = 0 then DoCtrlCreate(P) else if StrComp(Cmd, 'CTRL.SET') = 0 then DoCtrlSet(P) else if StrComp(Cmd, 'EVENT.BIND') = 0 then DoEventBind(P) else if StrComp(Cmd, 'EVENT.UNBIND') = 0 then DoEventUnbind(P); end; { ----- Main loop entry point ---------------------------------------------- } procedure TFormClient.ProcessMessages; var BytesRead: Integer; begin repeat BytesRead := FTransport.ReadMessage(FMsgBuf, MaxMsgLen - 1); if BytesRead > 0 then begin FMsgBuf[BytesRead] := #0; DispatchCommand(FMsgBuf); end; until BytesRead <= 0; end; end.