unit scrollingtext; {$mode delphi} interface uses Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, ExtCtrls, LCLIntf, strutils; function SearchAndReplaceStr(const AStr, ASearchStr, AReplaceStr: AnsiString): string; stdcall; function FormatFromList(const AFrmtdStr: string; const AArgs: TStrings): string; stdcall; type TScrollingText = class(TGraphicControl) private FActive: boolean; FActiveLine: integer; FBuffer: TBitmap; FEndLine: integer; FLineHeight: integer; FLines: TStrings; FNumLines: integer; FOffset: integer; FStartLine: integer; FStepSize: integer; FTimer: TTimer; FReplaceList: TStrings; function ActiveLineIsURL: boolean; procedure DoTimer(Sender: TObject); procedure Init; procedure DrawScrollingText(Sender: TObject); function GetFont (var VString: string): TFont; protected procedure SetActive(const AValue: boolean); procedure DoOnChangeBounds; override; procedure MouseDown(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override; procedure MouseMove(Shift: TShiftState; X,Y: Integer); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; property Active: boolean read FActive write SetActive; property Align; property Height; property Left; property Lines: TStrings read FLines write FLines; property ReplaceList: TStrings read FReplaceList write FReplaceList; property Top; property Width; end; implementation function SearchAndReplaceStr(const AStr, ASearchStr, AReplaceStr: AnsiString): string; var CurrDelim, NextDelim: Integer; ElemName, s: string; begin s:= ''; CurrDelim:= 1; repeat NextDelim:= PosEx(ASearchStr, AStr, CurrDelim); if NextDelim = 0 then NextDelim:= Length(AStr) + 1; ElemName:= Copy(AStr, CurrDelim, NextDelim - CurrDelim); if not (NextDelim > Length(AStr)) then s:= s + ElemName + AReplaceStr else s:= s + ElemName; CurrDelim:= NextDelim + Length(ASearchStr); until CurrDelim > Length(AStr); Result:= s; end; function FormatFromList(const AFrmtdStr: string; const AArgs: TStrings): string; var i: integer; s: string; begin s:= AFrmtdStr; if AArgs.Count > 0 then for i:= 0 to AArgs.Count - 1 do s:= SearchAndReplaceStr(s, AArgs.Names[i], AArgs.ValueFromIndex[i]); Result:= s; end; function TScrollingText.ActiveLineIsURL: boolean; begin if (FActiveLine > 0) and (FActiveLine < FLines.Count) then Result:= (Pos('http://', FLines[FActiveLine]) = 1) or (Pos('https://', FLines[FActiveLine]) = 1) else Result:= False; end; procedure TScrollingText.DoTimer(Sender: TObject); var w, i, j, k: integer; s, t, FS: string; begin if not Active then Exit; Dec(FOffset, FStepSize); if FOffSet < 0 then FStartLine:= -FOffset div FLineHeight else FStartLine:= 0; FEndLine:= FStartLine + FNumLines + 1; if FEndLine > FLines.Count - 1 then FEndLine:= FLines.Count - 1; FBuffer.Canvas.FillRect(Rect(0, 0, FBuffer.Width, FBuffer.Height)); for i:= FEndLine downto FStartLine do begin s:= Trim(FLines[i]); FBuffer.Canvas.Font.Style:= []; FBuffer.Canvas.Font.Color:= clBlack; if Length(s) > 0 then begin if (Pos('http://', s) = 1) or (Pos('https://', s) = 1) then begin if i = FActiveLine then begin FBuffer.Canvas.Font.Style:= [fsUnderline]; FBuffer.Canvas.Font.Color:= clRed; end else FBuffer.Canvas.Font.Color:= clBlue; end else FBuffer.Canvas.Font:= GetFont(s); s:= FormatFromList(s, ReplaceList); w:= FBuffer.Canvas.TextWidth(s); FBuffer.Canvas.TextOut((FBuffer.Width - w) div 2, FOffset + i * FLineHeight, s); end; end; if FStartLine > FLines.Count - 1 then FOffset:= FBuffer.Height; Invalidate; end; procedure TScrollingText.SetActive(const AValue: boolean); begin FActive:= AValue; if FActive then Init; FTimer.Enabled:= Active; end; procedure TScrollingText.Init; begin FBuffer.Width:= Width; FBuffer.Height:= Height; FLineHeight:= FBuffer.Canvas.TextHeight('X'); FNumLines:= FBuffer.Height div FLineHeight; if FOffset = -1 then FOffset:= FBuffer.Height; with FBuffer.Canvas do begin Brush.Color:= clWhite; Brush.Style:= bsSolid; FillRect(0, 0, Width, Height); end; end; procedure TScrollingText.DrawScrollingText(Sender: TObject); begin if Active then Canvas.Draw(0, 0, FBuffer); end; procedure TScrollingText.DoOnChangeBounds; begin inherited DoOnChangeBounds; Init; end; procedure TScrollingText.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin inherited MouseDown(Button, Shift, X, Y); if ActiveLineIsURL then OpenURL(FLines[FActiveLine]); end; procedure TScrollingText.MouseMove(Shift: TShiftState; X, Y: Integer); begin inherited MouseMove(Shift, X, Y); FActiveLine:= (Y - FOffset) div FLineHeight; Cursor:= crDefault; if (FActiveLine >= 0) and (FActiveLine < FLines.Count) and ActiveLineIsURL then Cursor:= crHandPoint; end; function TScrollingText.GetFont (var VString: string): TFont; var FS, FSt: string; i, EndPos: integer; F: TFont; begin EndPos:= 0; F:= TFont.Create; F.Assign(Font); if (LowerCase(Copy(VString, 0, 5)) = '{font') then begin EndPos:= Pos('}', VString) + 1; FS:= LowerCase(Copy(VString, 0, EndPos-1)); i:= Pos('name:', FS); if (i > 0) then F.Name:= Copy(FS, i+5, PosEx(';', FS, i+5)-i-5); i:= Pos('size:', FS); if (i > 0) then F.Size:= StrToInt(Copy(FS, i+5, PosEx(';', FS, i+5)-i-5)); i:= Pos('style:', FS); if (i > 0) then begin FSt:= Copy(FS, i+6, PosEx(';', FS, i+6)-i-6); if (Pos('bold', LowerCase(FSt)) > 0) then F.Style:= F.Style + [fsBold]; if (Pos('italic', LowerCase(FSt)) > 0) then F.Style:= F.Style + [fsItalic]; if (Pos('underline', LowerCase(FSt)) > 0) then F.Style:= F.Style + [fsUnderline]; if (Pos('strikeout', LowerCase(FSt)) > 0) then F.Style:= F.Style + [fsStrikeOut]; end; end; VString:= Copy(VString, EndPos, Length(VString)-EndPos+1); Result:= F; end; constructor TScrollingText.Create(AOwner: TComponent); begin inherited Create(AOwner); ControlStyle:= ControlStyle + [csOpaque]; OnPaint:= DrawScrollingText; FLines:= TStringList.Create; FReplaceList:= TStringList.Create; FTimer:= TTimer.Create(nil); FTimer.OnTimer:= DoTimer; FTimer.Interval:= 30; FBuffer:= TBitmap.Create; FStepSize:= 1; FStartLine:= 0; FOffset:= -1; Width:= 200; Height:= 200; end; destructor TScrollingText.Destroy; begin FLines.Free; FReplaceList.Free; FTimer.Free; FBuffer.Free; inherited Destroy; end; end.