{
Double Commander
-------------------------------------------------------------------------
Show file in the text, bin, hex or dec mode
Copyright (C) 2004 Radek Cervinka (radek.cervinka@centrum.cz)
Copyright (C) 2006-2025 Alexander Koblov (alexx2000@mail.ru)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
}
(*
TODO:
a) File mapping blocks writing into file by other processes.
Either:
+ Open small text files by reading them all into memory (done).
- Add optional custom loading/caching portions of file in memory
and only reading from file when neccessary.
b) Selecting text does not work well with composed Unicode characters
(characters that are composed of multiple Unicode characters).
c) Drawing/selecting text does not work correctly with RTL (right to left) text.
d) FTextHeight is unreliable with complex unicode characters. It should be
calculated based on currently displayed text (get max from each line's height).
*)
unit ViewerControl;
{$mode objfpc}{$H+}
interface
uses
SysUtils, Classes, Controls, StdCtrls, LCLVersion, LMessages, fgl;
const
MaxMemSize = $400000; // 4 Mb
type
TViewerControlMode = (vcmBin, vcmHex, vcmText, vcmWrap, vcmBook, vcmDec);
TDataAccess = (dtMmap, dtNothing);
TCharSide = (csBefore, csLeft, csRight, csAfter);
TPtrIntList = specialize TFPGList;
TGuessEncodingEvent = function(const s: string): string;
TFileOpenEvent = function(const FileName: String; Mode: LongWord): System.THandle;
TCustomCharsPresentation = class;
TCharToCustomValueTransformProc = function(AChar:AnsiChar;AMaxDigitsCount:integer):AnsiString of object;
{ TCustomCharsPresentation }
{
Presentation one char is called Value
Function for convert char to Value is ChrToValueProc
}
TCustomCharsPresentation = class
public
ValuesPerLine :integer; // = 16 for Hex by default
MaxValueDigits :integer; // the max width of present char (255) - 3 symbols
MaxAddrDigits :integer; // = 8;
StartOfs :integer; // = OffsetWidth + 2; // ': '
EndOfs :integer; // = StartOfs + (ValuesPerLine * (ValueMaxDigits+SpaceCount));
StartAscii :integer; // = StartOfs + (ValuesPerLine * (ValueMaxDigits+SpaceCount)) + 2; // ' '
SpaceCount :integer; // = 1 - one spacebar between Values
SeparatorSpace :AnsiString; // spacebar * SpaceCount
SeparatorChar :AnsiChar; // '|'
CountSeperate :integer; // insert SeparatorChar after every CountSeperate values
ChrToValueProc :TCharToCustomValueTransformProc; // procedure which return presentation of one char
constructor Create(APresentValuesPerLine,ACharMaxPresentWidth,AOffsetWidth,ACountSeparate:integer;AChrToValueProc:TCharToCustomValueTransformProc);
destructor Destroy();override;
end;
type
// If additional encodings are added they should be also supported by:
// - GetNextCharAsAscii
// - GetPrevCharAsAscii
// - GetNextCharAsUtf8
// - ConvertToUTF8
// - UpdateSelection
TViewerEncoding = (veAutoDetect,
veUtf8,
veUtf8bom,
veAnsi,
veOem,
veCp1250,
veCp1251,
veCp1252,
veCp1253,
veCp1254,
veCp1255,
veCp1256,
veCp1257,
veCp1258,
veCp437,
veCp850,
veCp852,
veCp866,
veCp874,
veCp932,
veCp936,
veCp949,
veCp950,
veIso88591,
veIso88592,
veKoi8r,
veKoi8u,
veKoi8ru,
veUcs2le,
veUcs2be,
veUtf16le,
veUtf16be,
veUtf32le, // = ucs4le
veUtf32be); // = ucs4be
TViewerEncodings = set of TViewerEncoding;
const
ViewerEncodingsNames: array [TViewerEncoding] of string =
('Auto-detect',
'UTF-8',
'UTF-8BOM',
'ANSI',
'OEM',
'CP1250',
'CP1251',
'CP1252',
'CP1253',
'CP1254',
'CP1255',
'CP1256',
'CP1257',
'CP1258',
'CP437',
'CP850',
'CP852',
'CP866',
'CP874',
'CP932',
'CP936',
'CP949',
'CP950',
'ISO-8859-1',
'ISO-8859-2',
'KOI8-R',
'KOI8-U',
'KOI8-RU',
'UCS-2LE',
'UCS-2BE',
'UTF-16LE',
'UTF-16BE',
'UTF-32LE',
'UTF-32BE');
const
ViewerEncodingOem: TViewerEncodings = [
veCp437, veCp850, veCp852, veCp866];
ViewerEncodingMultiByte: TViewerEncodings = [
veCp932, veCp936, veCp949, veCp950,
veUtf8, veUtf8bom, veUcs2le, veUcs2be,
veUtf16le, veUtf16be, veUtf32le, veUtf32be];
ViewerEncodingDoubleByte: TViewerEncodings = [
veUcs2le, veUcs2be, veUtf16le, veUtf16be ];
type
{ TViewerControl }
TViewerControl = class(TCustomControl)
protected
FEncoding: TViewerEncoding;
FViewerControlMode: TViewerControlMode;
FFileName: String;
FFileHandle: THandle;
FFileSize: Int64;
FMappingHandle: THandle;
FMappedFile: Pointer;
FPosition: PtrInt;
FHPosition: Integer; // Tab for text during horizontal scroll
FHLowEnd: Integer; // End for HPosition (string with max char)
FVisibleOffset: PtrInt; // Offset in symbols for current line (see IsVisible and MakeVisible)
FLowLimit: PtrInt; // Lowest possible value for Position
FHighLimit: PtrInt; // Position cannot reach this value
FBOMLength: Integer;
FLineList: TPtrIntList;
FBlockBeg: PtrInt;
FBlockEnd: PtrInt;
FCaretPos: PtrInt;
FCaretPoint: TPoint;
FMouseBlockBeg: PtrInt;
FMouseBlockSide: TCharSide;
FSelecting: Boolean;
FTextWidth: Integer; // max char count or width in window
FTextHeight: Integer; // measured values of font, rec calc at font changed
FScrollBarVert: TScrollBar;
FScrollBarHorz: TScrollBar;
FOnPositionChanged: TNotifyEvent;
FUpdateScrollBarPos: Boolean; // used to block updating of scrollbar
FScrollBarPosition: Integer; // for updating vertical scrollbar based on Position
FHScrollBarPosition: Integer; // for updating horizontal scrollbar based on HPosition
FColCount: Integer;
FTabSpaces: Integer; // tab width in spaces
FMaxTextWidth: Integer; // maximum of chars on one line unwrapped text (max 16384)
FExtraLineSpacing: Integer;
FLeftMargin: Integer;
FOnGuessEncoding: TGuessEncodingEvent;
FOnFileOpen: TFileOpenEvent;
FCaretVisible: Boolean;
FShowCaret: Boolean;
FAutoCopy: Boolean;
FLastError: String;
FText: String;
FHex:TCustomCharsPresentation;
FDec:TCustomCharsPresentation;
FCustom:TCustomCharsPresentation;
function GetPercent: Integer;
procedure SetPercent(const AValue: Integer);
procedure SetBlockBegin(const AValue: PtrInt);
procedure SetBlockEnd(const AValue: PtrInt);
procedure SetPosition(Value: PtrInt); virtual;
procedure SetHPosition(Value: Integer);
procedure SetPosition(Value: PtrInt; Force: Boolean); overload;
procedure SetHPosition(Value: Integer; Force: Boolean); overload;
procedure SetEncoding(AEncoding: TViewerEncoding);
function GetEncodingName: string;
procedure SetEncodingName(AEncodingName: string);
procedure SetViewerMode(Value: TViewerControlMode);
procedure SetColCount(const AValue: Integer);
procedure SetMaxTextWidth(const AValue: Integer);
procedure SetTabSpaces(const AValue: Integer);
procedure SetShowCaret(AValue: Boolean);
procedure SetCaretPos(AValue: PtrInt);
{en
Returns how many lines (given current FTextHeight) will fit into the window.
}
function GetClientHeightInLines(Whole: Boolean = True): Integer; inline;
{en
Calculates how many lines can be displayed from given position.
param(FromPosition
Position from which to check. It should point to a start of a line.)
@param(LastLineReached
If it is set to @true when the function returns, then the last
line of text was reached when scanning.
This means that there are no more lines to be displayed other than
the ones scanned from FromPosition. In other words:
SetPosition(GetStartOfNextLine(FromPosition)) will be one line
too many and will be scrolled back.)
}
function GetLinesTillEnd(FromPosition: PtrInt; out LastLineReached: Boolean): Integer;
function GetBomLength: Integer;
procedure UpdateLimits;
{en
@param(iStartPos
Should point to start of a line.
It is increased by the amount of parsed data (with line endings).)
@param(aLimit
Position which cannot be reached while reading from file.)
@param(DataLength
It is length in bytes of parsed data without any line endings.
iStartPos is moved beyond the line endings though.)
}
function CalcTextLineLength(var iStartPos: PtrInt; const aLimit: Int64; out DataLength: PtrInt): Integer;
function GetStartOfLine(aPosition: PtrInt): PtrInt;
function GetEndOfLine(aPosition: PtrInt): PtrInt;
function GetStartOfPrevLine(aPosition: PtrInt): PtrInt;
function GetStartOfNextLine(aPosition: PtrInt): PtrInt;
{en
Changes the value of aPosition to X lines back or forward.
@param(aPosition
File position to change.)
@param(iLines
Nr of lines to scroll.
If positive the position is increased by iLines lines,
if negative the position is decreased by -iLines lines.)
}
function ScrollPosition(var aPosition: PtrInt; iLines: Integer): Boolean;
{en
Calculates (x,y) cursor position to a position within file.
@param(x
Client X coordinate of mouse cursor.)
@param(y
Client Y coordinate of mouse cursor.)
@param(CharSide
To which side of a character at returned position the (x,y) points to.
Only valid if returned position is not -1.)
@returns(Position in file to which (x,y) points to, based on what is
currently displayed.
Returns -1 if (x,y) doesn't point to any position (outside of
the text for example).)
}
function XYPos2Adr(x, y: Integer; out CharSide: TCharSide): PtrInt;
procedure OutText(x, y: Integer; const sText: String; StartPos: PtrInt; DataLength: Integer);
procedure OutBin(x, y: Integer; const sText: String; StartPos: PtrInt; DataLength: Integer);
procedure OutCustom(x, y: Integer; const sText: String;StartPos: PtrInt; DataLength: Integer); // render one line
function TransformCustom(var APosition: PtrInt; ALimit: PtrInt; AWithAdditionalData: Boolean = True): String;
function TransformCustomBlock(var APosition: PtrInt; DataLength: Integer; ASeparatorsOn, AAlignData: Boolean; out AChars: String): String;
function HexToValueProc(AChar:AnsiChar;AMaxDigitsCount:integer):AnsiString;
function DecToValueProc(AChar:AnsiChar;AMaxDigitsCount:integer):AnsiString;
procedure WriteBin;
procedure WriteText;
procedure WriteCustom; virtual;
function TransformText(const sText: String; const Xoffset: Integer): String;
function TransformBin(var aPosition: PtrInt; aLimit: PtrInt): String;
function TransformHex(var aPosition: PtrInt; aLimit: PtrInt): AnsiString;virtual;
procedure AddLineOffset(const iOffset: PtrInt); inline;
procedure DrawLastError;
function MapFile(const sFileName: String): Boolean;
procedure UnMapFile;
procedure SetFileName(const sFileName: String);
procedure UpdateScrollbars;
procedure ViewerResize(Sender: TObject);
{en
Returns next unicode character from the file, depending on Encoding.
It is a faster version, which does as little conversion as possible,
but only Ascii values are guaranteed to be valid (0-127).
Other unicode values may/may not be valid, so shouldn't be tested.
This function is used for reading pure ascii characters such as
line endings, tabs, white spaces, etc.
}
function GetNextCharAsAscii(const iPosition: PtrInt; out CharLenInBytes: Integer): Cardinal;
function GetPrevCharAsAscii(const iPosition: PtrInt; out CharLenInBytes: Integer): Cardinal;
{en
Retrieve next character from the file depending on encoding and
automatically convert it to UTF-8.
If CharLenInBytes is greater than 0 but the result is an empty string
then it's possible there was no appropriate UTF-8 character for the
next character of the current encoding.
}
function GetNextCharAsUtf8(const iPosition: PtrInt; out CharLenInBytes: Integer): String;
procedure ReReadFile;
{en
Searches for an ASCII character.
@param(aPosition
Position from where the search starts.)
@param(aMaxBytes
How many bytes are available for reading.)
@param(AsciiChars
The function searches for any character that this string contains.)
@param(bFindNotIn
If @true searches for first character not included in AsciiChars.
If @false searches for first character included in AsciiChars.)
}
function FindAsciiSetForward(aPosition, aMaxBytes: PtrInt;
const AsciiChars: String;
bFindNotIn: Boolean): PtrInt;
{en
Same as FindForward but it searches backwards from pAdr.
aMaxBytes must be number of available bytes for reading backwards from pAdr.
}
function FindAsciiSetBackward(aPosition, aMaxBytes: PtrInt;
const AsciiChars: String;
bFindNotIn: Boolean): PtrInt;
{en
Checks if current selection is still valid given current viewer mode and encoding.
For example checks if selection is not in the middle of a unicode character.
}
procedure UpdateSelection;
function GetViewerRect: TRect;
procedure ScrollBarVertScroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: Integer);
procedure ScrollBarHorzScroll(Sender: TObject; ScrollCode: TScrollCode;
var ScrollPos: Integer);
function GetText(const StartPos, Len: PtrInt; const Xoffset: Integer): string;
procedure SetText(const AValue: String);
protected
procedure WMSetFocus(var Message: TLMSetFocus); message LM_SETFOCUS;
procedure WMKillFocus(var Message: TLMKillFocus); message LM_KILLFOCUS;
procedure FontChanged(Sender: TObject); override;
procedure KeyDown(var Key: word; Shift: TShiftState); override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
function DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean; override;
function DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean; override;
function DoMouseWheelLeft(Shift: TShiftState; MousePos: TPoint): Boolean; override;
function DoMouseWheelRight(Shift: TShiftState; MousePos: TPoint): Boolean; override;
procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy; const AXProportion, AYProportion: Double); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Paint; override;
{en
Scrolls the displayed text in the window.
@param(iLines
Nr of lines to scroll.
If positive the text is scrolled downwards,
if negative the text is scrolled upwards.)
@returns(@true if the text was scrolled.)
}
function Scroll(iLines: Integer): Boolean;
function HScroll(iSymbols: Integer): Boolean;
procedure PageUp;
procedure PageDown;
procedure GoHome;
procedure GoEnd;
procedure HPageUp;
procedure HPageDown;
procedure HGoHome;
procedure HGoEnd;
procedure CaretGoHome;
procedure CaretGoEnd;
function GetDataAdr: Pointer;
procedure SelectAll;
procedure SelectText(AStart, AEnd: PtrInt);
procedure CopyToClipboard;
procedure CopyToClipboardF;
function Selection: String;
function IsVisible(const aPosition: PtrInt): Boolean; overload;
procedure MakeVisible(const aPosition: PtrInt);
function ConvertToUTF8(const sText: AnsiString): String;
function ConvertFromUTF8(const sText: String): AnsiString;
function FindUtf8Text(iStartPos: PtrInt; const sSearchText: String;
bCaseSensitive: Boolean; bSearchBackwards: Boolean): PtrInt;
procedure ResetEncoding;
function IsFileOpen: Boolean; inline;
function DetectEncoding: TViewerEncoding;
procedure GetSupportedEncodings(List: TStrings);
property Text: String read FText write SetText;
property Percent: Integer Read GetPercent Write SetPercent;
property Position: PtrInt Read FPosition Write SetPosition;
property FileSize: Int64 Read FFileSize;
property FileHandle: THandle read FFileHandle;
property CaretPos: PtrInt Read FCaretPos Write SetCaretPos;
property SelectionStart: PtrInt Read FBlockBeg Write SetBlockBegin;
property SelectionEnd: PtrInt Read FBlockEnd Write SetBlockEnd;
property EncodingName: string Read GetEncodingName Write SetEncodingName;
property ColCount: Integer Read FColCount Write SetColCount;
property MaxTextWidth: Integer read FMaxTextWidth write SetMaxTextWidth;
property TabSpaces: Integer read FTabSpaces write SetTabSpaces;
property LeftMargin: Integer read FLeftMargin write FLeftMargin;
property ExtraLineSpacing: Integer read FExtraLineSpacing write FExtraLineSpacing;
property AutoCopy: Boolean read FAutoCopy write FAutoCopy;
property OnGuessEncoding: TGuessEncodingEvent Read FOnGuessEncoding Write FOnGuessEncoding;
property OnFileOpen: TFileOpenEvent read FOnFileOpen write FOnFileOpen;
published
property Mode: TViewerControlMode Read FViewerControlMode Write SetViewerMode default vcmWrap;
property FileName: String Read FFileName Write SetFileName;
property Encoding: TViewerEncoding Read FEncoding Write SetEncoding default veAutoDetect;
property OnPositionChanged: TNotifyEvent Read FOnPositionChanged Write FOnPositionChanged;
property ShowCaret: Boolean read FShowCaret write SetShowCaret;
property OnClick;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnMouseWheelUp;
property OnMouseWheelDown;
property Align;
property Color;
property Cursor default crIBeam;
property Font;
property ParentColor default False;
property TabStop default True;
end;
procedure Register;
implementation
uses
Math, LCLType, Graphics, Forms, LCLProc, Clipbrd, LConvEncoding,
DCUnicodeUtils, LCLIntf, LazUTF8, DCOSUtils , DCConvertEncoding
{$IF LCL_FULLVERSION >= 4990000}
, LazUTF16
{$ENDIF}
{$IF DEFINED(UNIX)}
, BaseUnix, Unix, DCUnix
{$ELSEIF DEFINED(WINDOWS)}
, Windows, DCWindows
{$ENDIF};
const
cBinWidth = 80;
// These strings must be Ascii only.
sNonCharacter: string = ' !"#$%&''()*+,-./:;<=>?@[\]^`{|}~'#13#10#9;
sWhiteSpace : string = ' '#13#10#9#8;
const
ASCII_TABLE: array[0..31] of String =
(
'.', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '◙', '♂', '♀', '♪', '♫', '☼',
'►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼'
);
{ TCustomCharsPresentation }
constructor TCustomCharsPresentation.Create(APresentValuesPerLine,
ACharMaxPresentWidth, AOffsetWidth, ACountSeparate: integer;AChrToValueProc:TCharToCustomValueTransformProc);
begin
SpaceCount:=1; // count of spacebars between values, =1
ValuesPerLine := APresentValuesPerLine; // default for hex: 16 values
MaxAddrDigits := AOffsetWidth; // = 8 , count of symbols for display caret offset
StartOfs := AOffsetWidth + 2; // ': '
MaxValueDigits := ACharMaxPresentWidth; // hex char (FF) - 2 symbols, dec char (255) - 3 symbols
EndOfs := StartOfs + (ValuesPerLine * (MaxValueDigits+SpaceCount)); // +1 - take in spacebar
StartAscii := StartOfs + (ValuesPerLine * (MaxValueDigits+SpaceCount)) + 2; // ' '
SeparatorChar:='|';
CountSeperate:=ACountSeparate;
SeparatorSpace:=' ';
ChrToValueProc:=AChrToValueProc; // method for convert char to Value
end;
destructor TCustomCharsPresentation.Destroy;
begin
inherited;
end;
// ----------------------------------------------------------------------------
constructor TViewerControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Cursor := crIBeam;
ParentColor := False;
DoubleBuffered := True;
ControlStyle := ControlStyle + [csTripleClicks, csOpaque];
TabStop := True; // so that it can get keyboard focus
FEncoding := veAutoDetect;
FViewerControlMode := vcmText;
FCustom := nil;
FFileName := '';
FMappedFile := nil;
FFileHandle := 0;
FMappingHandle := 0;
FPosition := 0;
FHPosition := 0;
FHLowEnd := 0;
FLowLimit := 0;
FHighLimit := 0;
FBOMLength := 0;
FTextHeight:= 14; // dummy value
FColCount := 1;
FTabSpaces := 8;
FLeftMargin := 4;
FMaxTextWidth := 1024;
FAutoCopy := True;
FLineList := TPtrIntList.Create;
FScrollBarVert := TScrollBar.Create(Self);
FScrollBarVert.Parent := Self;
FScrollBarVert.Kind := sbVertical;
FScrollBarVert.Align := alRight;
FScrollBarVert.OnScroll := @ScrollBarVertScroll;
FScrollBarVert.TabStop := False;
FScrollBarVert.PageSize := 0;
FScrollBarHorz := TScrollBar.Create(Self);
FScrollBarHorz.Parent := Self;
FScrollBarHorz.Kind := sbHorizontal;
FScrollBarHorz.Align := alBottom;
FScrollBarHorz.OnScroll := @ScrollBarHorzScroll;
FScrollBarHorz.TabStop := False;
FScrollBarHorz.PageSize := 0;
FUpdateScrollBarPos := True;
FScrollBarPosition := 0;
FHScrollBarPosition := 0;
FOnPositionChanged := nil;
FOnGuessEncoding := nil;
OnResize := @ViewerResize;
FHex:=TCustomCharsPresentation.Create(16,2,8,8,@HexToValueProc);
FDec:=TCustomCharsPresentation.Create(15,3,8,5,@DecToValueProc); // for set bigger ValuePerLine need to improve method GetEndOfLine
end;
destructor TViewerControl.Destroy;
begin
FHex.Free;
FDec.Free;
FHex:=nil;
FDec:=nil;
FCustom:=nil;
UnMapFile;
if Assigned(FLineList) then
FreeAndNil(FLineList);
inherited Destroy;
end;
procedure TViewerControl.DrawLastError;
var
AStyle: TTextStyle;
begin
AStyle:= Canvas.TextStyle;
AStyle.Alignment:= taCenter;
AStyle.Layout:= tlCenter;
Canvas.Pen.Color := Canvas.Font.Color;
Canvas.Line(0, 0, ClientWidth - 1, ClientHeight - 1);
Canvas.Line(0, ClientHeight - 1, ClientWidth - 1, 0);
Canvas.TextRect(GetViewerRect, 0, 0, FLastError, AStyle);
end;
procedure TViewerControl.Paint;
var
AText: String;
begin
if not IsFileOpen then
begin
DrawLastError;
Exit;
end;
if FShowCaret and FCaretVisible then
begin
FCaretPoint.X := -1;
FCaretVisible := not LCLIntf.HideCaret(Handle);
end;
Canvas.Font := Self.Font;
Canvas.Brush.Color := Self.Color;
{$IF DEFINED(LCLQT) and (LCL_FULLVERSION < 093100)}
Canvas.Brush.Style := bsSolid;
Canvas.FillRect(ClientRect);
{$ENDIF}
Canvas.Brush.Style := bsClear;
FTextHeight := Canvas.TextHeight('Wg') + FExtraLineSpacing;
if FViewerControlMode = vcmBook then
FTextWidth := ((ClientWidth - (Canvas.TextWidth('W') * FColCount)) div FColCount)
else begin
AText := StringOfChar('W', FMaxTextWidth);
FTextWidth := Canvas.TextFitInfo(AText, GetViewerRect.Width - FLeftMargin);
end;
FLineList.Clear;
case FViewerControlMode of
vcmBin : WriteBin;
vcmText: WriteText;
vcmWrap: WriteText;
vcmBook: WriteText;
vcmDec,vcmHex : WriteCustom;
end;
if FShowCaret and (FCaretPoint.X > -1) then
begin
LCLIntf.SetCaretPos(FCaretPoint.X, FCaretPoint.Y);
if not FCaretVisible then FCaretVisible:= LCLIntf.ShowCaret(Handle);
end;
end;
procedure TViewerControl.SetViewerMode(Value: TViewerControlMode);
begin
if not (csDesigning in ComponentState) then
begin
FLineList.Clear; // do not use cache from previous mode
FViewerControlMode := Value;
case FViewerControlMode of
vcmHex: FCustom := FHex;
vcmDec: FCustom := FDec;
else
FCustom := nil;
end;
if not IsFileOpen then
Exit;
// Take limits into account for selection.
FBlockBeg := FBlockBeg + (GetDataAdr - FMappedFile);
FBlockEnd := FBlockEnd + (GetDataAdr - FMappedFile);
FHPosition := 0;
FBOMLength := GetBomLength;
UpdateLimits;
// Take limits into account for selection.
FBlockBeg := FBlockBeg - (GetDataAdr - FMappedFile);
FBlockEnd := FBlockEnd - (GetDataAdr - FMappedFile);
UpdateSelection;
// Force recalculating position.
SetPosition(FPosition, True);
SetHPosition(FHPosition, True);
UpdateScrollbars;
Invalidate;
end
else
FViewerControlMode := Value;
end;
procedure TViewerControl.SetColCount(const AValue: Integer);
begin
if AValue > 0 then FColCount := AValue
else FColCount := 1;
end;
procedure TViewerControl.SetMaxTextWidth(const AValue: Integer);
begin
if AValue < 80 then
FMaxTextWidth := 80
else if AValue > 16384 then
FMaxTextWidth := 16384
else
FMaxTextWidth:= AValue;
end;
procedure TViewerControl.SetTabSpaces(const AValue: Integer);
begin
if AValue < 1 then
FTabSpaces := 1
else if AValue > 32 then
FTabSpaces := 32
else
FTabSpaces := AValue;
end;
function TViewerControl.ScrollPosition(var aPosition: PtrInt; iLines: Integer): Boolean;
var
i: Integer;
NewPos: PtrInt;
begin
Result := False;
NewPos := aPosition;
if iLines < 0 then
for i := 1 to -iLines do
NewPos := GetStartOfPrevLine(NewPos)
else
for i := 1 to iLines do
NewPos := GetStartOfNextLine(NewPos);
Result := aPosition <> NewPos;
aPosition := NewPos;
end;
function TViewerControl.Scroll(iLines: Integer): Boolean;
var
aPosition: PtrInt;
begin
if not IsFileOpen then
Exit(False);
aPosition := FPosition;
Result := ScrollPosition(aPosition, iLines);
if aPosition <> FPosition then
SetPosition(aPosition);
end;
function TViewerControl.HScroll(iSymbols: Integer): Boolean;
var
newPos: Integer;
begin
if not IsFileOpen then
Exit(False);
newPos := FHPosition + iSymbols;
if newPos < 0 then
newPos := 0
else if (newPos > FHLowEnd - FTextWidth) and (FHLowEnd - FTextWidth > 0) then
newPos := FHLowEnd - FTextWidth;
if newPos <> FHPosition then
SetHPosition(newPos);
Result:= True;
end;
function TViewerControl.GetText(const StartPos, Len: PtrInt; const Xoffset: Integer): string;
begin
SetString(Result, GetDataAdr + StartPos, Len);
Result := TransformText(ConvertToUTF8(Result), Xoffset);
end;
procedure TViewerControl.SetText(const AValue: String);
begin
UnMapFile;
FText:= AValue;
FileName:= EmptyStr;
FFileSize:= Length(FText);
FMappedFile:= Pointer(FText);
end;
function TViewerControl.GetViewerRect: TRect;
begin
Result:= GetClientRect;
if Assigned(FScrollBarHorz) and FScrollBarHorz.Visible then
Dec(Result.Bottom, FScrollBarHorz.Height);
if Assigned(FScrollBarVert) and FScrollBarVert.Visible then
Dec(Result.Right, FScrollBarVert.Width);
end;
procedure TViewerControl.WMSetFocus(var Message: TLMSetFocus);
begin
if FShowCaret then
begin
LCLIntf.CreateCaret(Handle, 0, 2, FTextHeight);
LCLIntf.ShowCaret(Handle);
FCaretVisible:= True;
end;
end;
procedure TViewerControl.WMKillFocus(var Message: TLMKillFocus);
begin
if FShowCaret then
begin
FCaretVisible:= False;
LCLIntf.DestroyCaret(Handle);
end;
end;
procedure TViewerControl.FontChanged(Sender: TObject);
begin
inherited FontChanged(Sender);
if HandleAllocated then
begin
FTextHeight := Canvas.TextHeight('Wg') + FExtraLineSpacing;
if FShowCaret then LCLIntf.CreateCaret(Handle, 0, 2, FTextHeight);
end;
end;
function TViewerControl.CalcTextLineLength(var iStartPos: PtrInt; const aLimit: Int64; out DataLength: PtrInt): Integer;
var
MaxLineLength: Boolean;
CharLenInBytes: Integer;
OldPos, LastSpacePos: PtrInt;
LastSpaceResult: Integer;
begin
Result := 0;
DataLength := 0;
LastSpacePos := -1;
MaxLineLength := True;
OldPos := iStartPos;
while MaxLineLength and (iStartPos < aLimit) do
begin
case GetNextCharAsAscii(iStartPos, CharLenInBytes) of
9: // tab
Inc(Result, FTabSpaces - Result mod FTabSpaces);
10: // stroka
begin
DataLength := iStartPos - OldPos;
iStartPos := iStartPos + CharLenInBytes;
Exit;
end;
13: // karetka
begin
DataLength := iStartPos - OldPos;
iStartPos := iStartPos + CharLenInBytes;
// Move after possible #10.
if (iStartPos < aLimit) and (GetNextCharAsAscii(iStartPos, CharLenInBytes) = 10) then
Inc(iStartPos, CharLenInBytes);
Exit;
end;
32, 33, 40, 41, 44, 45, 46, 47, 92, 58, 59, 63, 91, 93: //probel
begin
Inc(Result, 1);
LastSpacePos := iStartPos + CharLenInBytes;
LastSpaceResult := Result;
end;
else
Inc(Result, 1);
end;
if CharLenInBytes = 0 then // End of data or invalid character.
break;
iStartPos := iStartPos + CharLenInBytes;
DataLength := iStartPos - OldPos;
case FViewerControlMode of
vcmText: MaxLineLength := Result < FMaxTextWidth;
vcmWrap: MaxLineLength := Result < FTextWidth;
vcmBook: MaxLineLength := Canvas.TextWidth(GetText(OldPos, DataLength, 0)) < FTextWidth;
else
Exit;
end;
end;
if (not MaxLineLength) and (LastSpacePos <> -1) then
begin
iStartPos := LastSpacePos;
Result := LastSpaceResult;
DataLength := iStartPos - OldPos;
end;
end;
function TViewerControl.TransformText(const sText: String; const Xoffset: Integer): String;
var
c: AnsiChar;
i: Integer;
Dos: Boolean;
begin
Result := '';
Dos:= FEncoding in ViewerEncodingOem;
for i := 1 to Length(sText) do
begin
c := sText[i];
// Parse only ASCII chars.
case c of
#9:
Result := Result + StringOfChar(' ',
FTabSpaces - (UTF8Length(Result) + Xoffset) mod FTabSpaces);
else
begin
if c < ' ' then
begin
if Dos then
Result := Result + ASCII_TABLE[Ord(c)]
else
Result := Result + ' ';
end
else
Result := Result + c;
end;
end;
end;
end;
function TViewerControl.TransformBin(var aPosition: PtrInt; aLimit: PtrInt): String;
var
S: String;
C: AnsiChar;
P: PAnsiChar;
Len: Integer;
I, L: Integer;
SingleByte: Boolean;
begin
Result := EmptyStr;
if (APosition + cBinWidth) > aLimit then
Len:= aLimit - APosition
else begin
Len:= cBinWidth;
end;
SetString(S, PAnsiChar(GetDataAdr) + aPosition, Len);
SingleByte:= not (FEncoding in ViewerEncodingMultiByte);
if SingleByte then
begin
S:= ConvertToUTF8(S);
end;
L:= Length(S);
P:= PAnsiChar(S);
for I := 1 to L do
begin
C := P^;
if C < ' ' then
Result := Result + '.'
else if SingleByte then
Result := Result + C
else if C > #127 then
Result := Result + '.'
else begin
Result := Result + C;
end;
Inc(P);
end;
Inc(aPosition, Len);
end;
function TViewerControl.TransformHex(var aPosition: PtrInt; aLimit: PtrInt): AnsiString;
begin
Result:=TransformCustom(aPosition,aLimit);
end;
function TViewerControl.TransformCustom(var APosition: PtrInt; ALimit: PtrInt;
AWithAdditionalData: boolean): String;
var
sAscii: string = '';
sRez : string = '';
tPos : integer;
begin
tPos:=APosition;
sRez:=TransformCustomBlock(APosition,FCustom.ValuesPerLine,True,True,sAscii);
// Result := LineFormat(sRez, sStr, aStartOffset) else
if AWithAdditionalData then
begin
sRez := Format('%s: %s', [IntToHex(tPos, FCustom.MaxAddrDigits), sRez]);
if Length(sRez) < FCustom.ValuesPerLine * (FCustom.SpaceCount+FCustom.MaxValueDigits) then
sRez := sRez + StringOfChar(' ', FCustom.ValuesPerLine * (FCustom.SpaceCount+FCustom.MaxValueDigits) - Length(sRez));
sRez := sRez + ' ';
sRez := sRez + sAscii;
end;
Result:=sRez;
end;
function TViewerControl.TransformCustomBlock(var APosition: PtrInt;
DataLength: Integer; ASeparatorsOn, AAlignData: Boolean; out AChars: String): String;
var
S: String;
C: AnsiChar;
P: PAnsiChar;
Len: Integer;
I, L: Integer;
sEmpty: String;
iSep: Integer = 1;
SingleByte: Boolean;
begin
Result:= EmptyStr;
if (APosition + DataLength) > FHighLimit then
Len:= FHighLimit - APosition
else begin
Len:= DataLength;
end;
SetString(S, PAnsiChar(GetDataAdr) + aPosition, Len);
SingleByte:= not (FEncoding in ViewerEncodingMultiByte);
if SingleByte then
begin
S:= ConvertToUTF8(S);
end;
L:= Length(S);
P:= PAnsiChar(S);
AChars:= EmptyStr;
for I := 1 to L do
begin
C := P^;
if C < ' ' then
AChars := AChars + '.'
else if SingleByte then
AChars := AChars + C
else if C > #127 then
AChars := AChars + '.'
else begin
AChars := AChars + C;
end;
Inc(P);
end;
P:= PAnsiChar(GetDataAdr);
for I := 0 to Len - 1 do
begin
C := P[aPosition];
Result += FCustom.ChrToValueProc(C, FCustom.MaxValueDigits);
if (iSep = FCustom.CountSeperate) and ASeparatorsOn and
(I < (FCustom.ValuesPerLine - 1))then
begin
iSep := 0;
Result += FCustom.SeparatorChar;
end else
begin
Result += FCustom.SeparatorSpace;
end;
Inc(aPosition);
Inc(iSep);
end;
if AAlignData then
begin
sEmpty := StringOfChar(#32, FCustom.MaxValueDigits);
while (I < FCustom.ValuesPerLine - 1) do
begin
Result += sEmpty + FCustom.SeparatorSpace;
Inc(I);
end;
end;
end;
function TViewerControl.DecToValueProc(AChar:AnsiChar;AMaxDigitsCount:integer):AnsiString;
begin
Result:= IntToStr(Ord(AChar));
while Length(Result) < AMaxDigitsCount do
Result:= '0' + Result;
end;
function TViewerControl.HexToValueProc(AChar:AnsiChar;AMaxDigitsCount:integer):AnsiString;
begin
Result:=IntToHex(Ord(AChar), AMaxDigitsCount);
while length(Result) FLowLimit) do
begin
prevChar := GetPrevCharAsAscii(tmpPos, CharLenInBytes);
if CharLenInBytes = 0 then
Break;
Dec(tmpPos, CharLenInBytes);
case prevChar of
9:
Inc(DataLength, FTabSpaces - DataLength mod FTabSpaces);
else
Inc(DataLength, 1);
end;
case FViewerControlMode of
vcmText: MaxLineLength := DataLength < FMaxTextWidth;
vcmWrap: MaxLineLength := DataLength < FTextWidth;
end;
if not MaxLineLength then Exit(tmpPos);
end;
// Previous end of line not found and there are no more data to check.
if (not (prevChar in [10, 13])) and (tmpPos <= FLowLimit) then
Exit(FLowLimit);
// Move forward to first non-line ending character.
Inc(tmpPos, CharLenInBytes);
// Search for start of real line or wrapped line.
while True do
begin
LineStartPos := tmpPos;
CalcTextLineLength(tmpPos, FHighLimit, DataLength);
if tmpPos = aPosition then
begin
if aPosition < FHighLimit then
Exit(aPosition) // aPosition is already at start of a line
else
Exit(LineStartPos); // aPosition points to end of file so return start of this line
end
else if tmpPos > aPosition then
Exit(LineStartPos); // Found start of line
end;
end;
function GetStartOfLineFixed(aFixedWidth: Integer): PtrInt;
begin
Result := aPosition - (aPosition mod aFixedWidth);
end;
var
i: Integer;
begin
if aPosition <= FLowLimit then
Exit(FLowLimit)
else if aPosition >= FHighLimit then
aPosition := FHighLimit; // search from the end of the file
// Speedup for currently displayed positions.
if (FLineList.Count > 0) and
(aPosition >= FLineList.Items[0]) and
(aPosition <= FLineList.Items[FLineList.Count - 1]) then
begin
for i := FLineList.Count - 1 downto 0 do
if FLineList.Items[i] <= aPosition then
Exit(FLineList.Items[i]);
end;
case FViewerControlMode of
vcmBin:
Result := GetStartOfLineFixed(cBinWidth);
vcmHex, vcmDec:
Result := GetStartOfLineFixed(FCustom.ValuesPerLine);
vcmText, vcmWrap, vcmBook:
Result := GetStartOfLineText;
else
Result := aPosition;
end;
end;
function TViewerControl.GetEndOfLine(aPosition: PtrInt): PtrInt;
function GetEndOfLineText: PtrInt;
var
tmpPos: PtrInt;
DataLength: PtrInt;
begin
Result := GetStartOfLine(aPosition);
tmpPos := Result;
CalcTextLineLength(tmpPos, FHighLimit, DataLength);
Result := Result + DataLength;
if Result < aPosition then
Result := aPosition;
end;
function GetEndOfLineFixed(aFixedWidth: Integer): PtrInt;
begin
Result := aPosition - (aPosition mod aFixedWidth) + aFixedWidth;
end;
begin
case FViewerControlMode of
vcmBin:
Result := GetEndOfLineFixed(cBinWidth);
vcmHex,vcmDec:
Result := GetEndOfLineFixed(FCustom.ValuesPerLine);
vcmText, vcmWrap, vcmBook:
Result := GetEndOfLineText;
else
Result := aPosition;
end;
end;
function TViewerControl.GetStartOfPrevLine(aPosition: PtrInt): PtrInt;
function GetPrevLineText: PtrInt;
var
tmpPos, LineStartPos: PtrInt;
DataLength: PtrInt;
prevChar: Cardinal;
MaxLineLength: Boolean;
CharLenInBytes: Integer;
begin
prevChar := GetPrevCharAsAscii(aPosition, CharLenInBytes);
if CharLenInBytes = 0 then
Exit(aPosition);
tmpPos := aPosition - CharLenInBytes; // start search from previous character
if tmpPos <= FLowLimit then
Exit(FLowLimit);
// Check if we're not in the middle of line ending
// (previous char is #13, current char is #10).
if (prevChar = 13) and
(GetNextCharAsAscii(aPosition, CharLenInBytes) = 10) then
begin
prevChar := GetPrevCharAsAscii(tmpPos, CharLenInBytes);
if CharLenInBytes = 0 then
Exit(aPosition);
Dec(tmpPos, CharLenInBytes);
end
else
begin
// Bypass possible end of previous line.
if prevChar = 10 then
begin
prevChar := GetPrevCharAsAscii(tmpPos, CharLenInBytes);
if CharLenInBytes = 0 then
Exit(aPosition);
Dec(tmpPos, CharLenInBytes);
end;
if prevChar = 13 then
begin
prevChar := GetPrevCharAsAscii(tmpPos, CharLenInBytes);
if CharLenInBytes = 0 then
Exit(aPosition);
Dec(tmpPos, CharLenInBytes);
end;
end;
if tmpPos <= FLowLimit then
Exit(FLowLimit);
DataLength:= 0;
// Search for real start of line.
while (not (prevChar in [10, 13])) and (tmpPos > FLowLimit) do
begin
prevChar := GetPrevCharAsAscii(tmpPos, CharLenInBytes);
if CharLenInBytes = 0 then
Break;
Dec(tmpPos, CharLenInBytes);
case prevChar of
9:
Inc(DataLength, FTabSpaces - DataLength mod FTabSpaces);
else
Inc(DataLength, 1);
end;
case FViewerControlMode of
vcmText: MaxLineLength := DataLength < FMaxTextWidth;
vcmWrap: MaxLineLength := DataLength < FTextWidth;
end;
if not MaxLineLength then Exit(tmpPos);
end;
// Move forward to first non-line ending character.
Inc(tmpPos, CharLenInBytes);
// Search for start of real line or wrapped line.
while True do
begin
LineStartPos := tmpPos;
CalcTextLineLength(tmpPos, aPosition, DataLength);
if tmpPos >= aPosition then
Exit(LineStartPos); // Found start of line
end;
end;
function GetPrevLineFixed(aFixedWidth: Integer): PtrInt;
begin
Result := aPosition - (aPosition mod aFixedWidth);
if Result >= aFixedWidth then
Result := Result - aFixedWidth;
end;
var
i: Integer;
begin
if aPosition <= FLowLimit then
Exit(FLowLimit)
else if aPosition >= FHighLimit then
aPosition := FHighLimit; // search from the end of the file
// Speedup for currently displayed positions.
if (FLineList.Count > 0) and
(aPosition >= FLineList.Items[0]) and
(aPosition <= FLineList.Items[FLineList.Count - 1]) then
begin
for i := FLineList.Count - 1 downto 0 do
if FLineList.Items[i] < aPosition then
Exit(FLineList.Items[i]);
end;
case FViewerControlMode of
vcmBin:
Result := GetPrevLineFixed(cBinWidth);
vcmHex,vcmDec:
Result := GetPrevLineFixed(FCustom.ValuesPerLine);
vcmText, vcmWrap, vcmBook:
Result := GetPrevLineText;
else
Result := aPosition;
end;
end;
function TViewerControl.GetStartOfNextLine(aPosition: PtrInt): PtrInt;
function GetNextLineText: PtrInt;
var
tmpPos: PtrInt;
DataLength: PtrInt;
prevChar: Cardinal;
CharLenInBytes: Integer;
begin
tmpPos := aPosition;
// This might not be a real start of line (it may be start of wrapped line).
// Search for start of line.
while (tmpPos > FLowLimit) do
begin
prevChar := GetPrevCharAsAscii(tmpPos, CharLenInBytes);
if CharLenInBytes = 0 then
Break;
if (prevChar in [10, 13]) then
Break
else
Dec(tmpPos, CharLenInBytes);
end;
// Now we know we are at the start of a line, search the start of next line.
while True do
begin
CalcTextLineLength(tmpPos, FHighLimit, DataLength);
if tmpPos >= aPosition then
Exit(tmpPos); // Found start of line
end;
end;
function GetNextLineFixed(aFixedWidth: Integer): PtrInt;
begin
Result := aPosition - (aPosition mod aFixedWidth);
if Result + aFixedWidth < FHighLimit then
Result := Result + aFixedWidth;
end;
var
i: Integer;
begin
if aPosition < FLowLimit then
aPosition := FLowLimit // search from the start of the file
else if aPosition >= FHighLimit then
aPosition := FHighLimit; // search from the end of the file
// Speedup for currently displayed positions.
if (FLineList.Count > 0) and
(aPosition >= FLineList.Items[0]) and
(aPosition <= FLineList.Items[FLineList.Count - 1]) then
begin
for i := 0 to FLineList.Count - 1 do
if FLineList.Items[i] > aPosition then
Exit(FLineList.Items[i]);
end;
case FViewerControlMode of
vcmBin:
Result := GetNextLineFixed(cBinWidth);
vcmHex,vcmDec:
Result := GetNextLineFixed(FCustom.ValuesPerLine);
vcmText, vcmWrap, vcmBook:
Result := GetNextLineText;
else
Result := aPosition;
end;
end;
procedure TViewerControl.PageUp;
var
H: Integer;
begin
H := GetClientHeightInLines * FColCount - 1;
if H <= 0 then
H := 1;
Scroll(-H);
end;
procedure TViewerControl.HPageUp;
var
H: Integer;
begin
H := FHPosition - FTextWidth;
if H <= 0 then
H := FHPosition else H:= FTextWidth;
HScroll(-H);
end;
procedure TViewerControl.PageDown;
var
H: Integer;
begin
H := GetClientHeightInLines * FColCount - 1;
if H <= 0 then
H := 1;
Scroll(H);
end;
procedure TViewerControl.HPageDown;
var
H: Integer;
begin
H := FHLowEnd - FHPosition;
if H > FTextWidth then H := FTextWidth ;
HScroll(H);
end;
procedure TViewerControl.GoHome;
begin
Position := FLowLimit;
end;
procedure TViewerControl.GoEnd;
begin
Position := FHighLimit;
end;
procedure TViewerControl.HGoHome;
begin
HScroll (-FHPosition);
end;
procedure TViewerControl.HGoEnd;
begin
HScroll (FHLowEnd-FHPosition);
end;
procedure TViewerControl.CaretGoHome;
begin
HScroll (-FHPosition);
CaretPos := GetStartOfLine(CaretPos);
end;
procedure TViewerControl.CaretGoEnd;
begin
if FViewerControlMode in [vcmBin, vcmHex, vcmDec] then
CaretPos := GetEndOfLine(CaretPos) - 1
else begin
CaretPos := GetEndOfLine(CaretPos);
end;
if FViewerControlMode = vcmText then
begin
if not IsVisible(CaretPos) then
begin
if (FVisibleOffset < FHPosition) or
(FVisibleOffset > FHPosition + FTextWidth) then
begin
SetHPosition(FVisibleOffset);
HScroll(-1);
end;
end;
end;
end;
procedure TViewerControl.SetFileName(const sFileName: String);
begin
if not (csDesigning in ComponentState) then
begin
UnMapFile;
if sFileName <> '' then
begin
if MapFile(sFileName) then
begin
FFileName := sFileName;
// Detect encoding if needed.
if FEncoding = veAutoDetect then
FEncoding := DetectEncoding;
ReReadFile;
CaretPos := FLowLimit;
end;
end;
end
else
FFileName := sFileName;
end;
function TViewerControl.MapFile(const sFileName: String): Boolean;
function ReadFile: Boolean; inline;
begin
FMappedFile := GetMem(FFileSize);
Result := (FileRead(FFileHandle, FMappedFile^, FFileSize) = FFileSize);
if not Result then
begin
FLastError := mbSysErrorMessage;
FreeMemAndNil(FMappedFile);
end;
FileClose(FFileHandle);
FFileHandle := 0;
end;
{$IFDEF LINUX}
var
Sbfs: TStatFS;
{$ENDIF}
begin
Result := False;
FLastError := EmptyStr;
if Assigned(FMappedFile) then
UnMapFile; // if needed
if Assigned(FOnFileOpen) then
FFileHandle := FOnFileOpen(sFileName, fmOpenRead or fmShareDenyNone)
else begin
FFileHandle := mbFileOpen(sFileName, fmOpenRead or fmShareDenyNone);
end;
if FFileHandle = feInvalidHandle then
begin
FLastError := mbSysErrorMessage;
FFileHandle := 0;
Exit;
end;
FFileSize := FileGetSize(FFileHandle);
if (FFileSize < 0) then
begin
FLastError := mbSysErrorMessage;
FileClose(FFileHandle);
FFileHandle := 0;
Exit;
end;
{$IFDEF LINUX}
if (fpFStatFS(FFileHandle, @Sbfs) = 0) then
begin
// Special case for PROC_FS and SYS_FS
if (sbfs.fstype = PROC_SUPER_MAGIC) or (sbfs.fstype = SYSFS_MAGIC) then
begin
FMappedFile := GetMem(MaxMemSize - 1);
FFileSize := FileRead(FFileHandle, FMappedFile^, MaxMemSize - 1);
Result := (FFileSize >= 0);
if not Result then
begin
FLastError := mbSysErrorMessage;
FreeMemAndNil(FMappedFile);
end;
FileClose(FFileHandle);
FFileHandle := 0;
Exit;
end;
end;
{$ENDIF}
if (FFileSize < MaxMemSize) then
begin
Result := ReadFile;
Exit;
end;
{$IFDEF MSWINDOWS}
FMappingHandle := CreateFileMapping(FFileHandle, nil, PAGE_READONLY, 0, 0, nil);
if FMappingHandle = 0 then
begin
FLastError := mbSysErrorMessage;
FMappedFile := nil;
UnMapFile;
end
else begin
FMappedFile := MapViewOfFile(FMappingHandle, FILE_MAP_READ, 0, 0, 0);
if (FMappedFile = nil) then
begin
FLastError := mbSysErrorMessage;
UnMapFile;
end;
end;
{$ELSE}
FMappedFile := fpmmap(nil, FFileSize, PROT_READ, MAP_PRIVATE{SHARED}, FFileHandle, 0);
if FMappedFile = MAP_FAILED then
begin
FLastError := mbSysErrorMessage;
FMappedFile:= nil;
FileClose(FFileHandle);
FFileHandle := 0;
Exit;
end;
{$ENDIF}
Result := Assigned(FMappedFile);
end;
procedure TViewerControl.UnMapFile;
begin
if FMappedFile = Pointer(FText) then
begin
FMappedFile:= nil;
FText:= EmptyStr;
end;
if (FFileSize < MaxMemSize) then
begin
if Assigned(FMappedFile) then
begin
FreeMem(FMappedFile);
FMappedFile := nil;
end;
end;
{$IFDEF MSWINDOWS}
if Assigned(FMappedFile) then
begin
UnmapViewOfFile(FMappedFile);
FMappedFile := nil;
end;
if FMappingHandle <> 0 then
begin
CloseHandle(FMappingHandle);
FMappingHandle := 0;
end;
{$ELSE}
if Assigned(FMappedFile) then
begin
if fpmunmap(FMappedFile, FFileSize) = -1 then
DebugLn('Error unmapping file: ', SysErrorMessage(fpgeterrno));
FMappedFile := nil;
end;
{$ENDIF}
if FFileHandle <> 0 then
begin
FileClose(FFileHandle);
FFileHandle := 0;
end;
FFileName := '';
FFileSize := 0;
Position := 0;
FLowLimit := 0;
FHighLimit := 0;
FBOMLength := 0;
FBlockBeg := 0;
FBlockEnd := 0;
end;
procedure TViewerControl.WriteText;
var
yIndex, xIndex, w, i: Integer;
LineStart, iPos: PtrInt;
CharLenInBytes: Integer;
DataLength: PtrInt;
sText: String;
procedure DrawCaret(X, Y: Integer; LinePos: PtrInt);
begin
if FShowCaret and (FCaretPos = LinePos) then
begin
FCaretPoint.X:= X;
FCaretPoint.Y:= Y;
end;
end;
begin
iPos := FPosition;
if Mode = vcmBook then
w := Width div FColCount
else begin
w := 0;
end;
for xIndex := 0 to FColCount-1 do
begin
for yIndex := 0 to GetClientHeightInLines(False) - 1 do
begin
if iPos > FHighLimit then
Break;
if iPos = FHighLimit then
begin
if GetPrevCharAsAscii(iPos, CharLenInBytes) = 10 then
begin
DrawCaret(0, yIndex * FTextHeight, iPos);
end;
Break;
end;
AddLineOffset(iPos);
LineStart := iPos;
i := CalcTextLineLength(iPos, FHighLimit, DataLength);
if i > FHLowEnd then FHLowEnd:= i;
if DataLength = 0 then
DrawCaret(0, yIndex * FTextHeight, LineStart)
else begin
if (Mode = vcmText) and (FHPosition > 0) then
begin
for i:= 1 to FHPosition do
begin
GetNextCharAsAscii(LineStart, CharLenInBytes);
DataLength -= CharLenInBytes;
LineStart += CharLenInBytes;
end;
if (DataLength <= 0) then Continue;
end;
sText := GetText(LineStart, DataLength, 0);
OutText(FLeftMargin + xIndex * w, yIndex * FTextHeight, sText, LineStart, DataLength);
end;
end;
end;
end;
procedure TViewerControl.WriteCustom;
// this method render visible page of text
var
yIndex: Integer;
iPos, LineStart: PtrInt;
s: string;
begin
iPos := FPosition;
for yIndex := 0 to GetClientHeightInLines(False) - 1 do
begin
if iPos >= FHighLimit then
Break;
LineStart := iPos;
AddLineOffset(iPos);
s := TransformCustom(iPos, FHighLimit); // get line text for render
if s <> '' then
OutCustom(FLeftMargin, yIndex * FTextHeight, s, LineStart, iPos - LineStart); // render line to canvas
end;
end;
procedure TViewerControl.WriteBin;
var
yIndex: Integer;
iPos, LineStart: PtrInt;
s: string;
begin
iPos := FPosition;
for yIndex := 0 to GetClientHeightInLines(False) - 1 do
begin
if iPos >= FHighLimit then
Break;
LineStart := iPos;
AddLineOffset(iPos);
s := TransformBin(iPos, FHighLimit);
if s <> '' then
OutBin(FLeftMargin, yIndex * FTextHeight, s, LineStart, iPos - LineStart);
end;
end;
function TViewerControl.GetDataAdr: Pointer;
begin
case FViewerControlMode of
vcmText, vcmWrap, vcmBook:
Result := FMappedFile + FBOMLength;
else
Result := FMappedFile;
end;
end;
procedure TViewerControl.SetPosition(Value: PtrInt);
begin
SetPosition(Value, False);
end;
procedure TViewerControl.SetHPosition(Value: Integer);
begin
SetHPosition(Value, False);
end;
procedure TViewerControl.SetHPosition(Value: Integer; Force: Boolean);
begin
if not IsFileOpen then
Exit;
FHPosition := Value;
// Set new scroll position.
if (FHPosition > 0) and (FHLowEnd - FTextWidth > 0) then
FHScrollBarPosition := FHPosition * 100 div (FHLowEnd - FTextWidth)
else
FHScrollBarPosition := 0;
// Update scrollbar position.
if FUpdateScrollBarPos then
begin
if FScrollBarHorz.Position <> FHScrollBarPosition then
begin
// Workaround for bug: http://bugs.freepascal.org/view.php?id=23815
{$IF DEFINED(LCLQT) and (LCL_FULLVERSION < 1010000)}
FScrollBarHorz.OnScroll := nil;
FScrollBarHorz.Position := FHScrollBarPosition;
Application.ProcessMessages; // Skip message
FScrollBarHorz.OnScroll := @ScrollBarHorzScroll;
{$ELSE}
FScrollBarHorz.Position := FHScrollBarPosition;
{$ENDIF}
end;
end;
// else the scrollbar position will be updated in ScrollBarVertScroll
Invalidate;
end;
procedure TViewerControl.SetPosition(Value: PtrInt; Force: Boolean);
var
LinesTooMany: Integer;
LastLineReached: Boolean;
begin
if not IsFileOpen then
Exit;
// Double byte text can have only even position
if (Encoding in ViewerEncodingDoubleByte) and Odd(Value) then
begin
Value := Value - 1;
end;
// Speedup if total nr of lines is less then nr of lines that can be displayed.
if (FPosition = FLowLimit) and // only if already at the top
(FLineList.Count > 0) and (FLineList.Count < GetClientHeightInLines)
then
Value := FLowLimit
else
// Boundary checks are done in GetStartOfLine.
Value := GetStartOfLine(Value);
if (Value <> FPosition) or Force then
begin
// Don't allow empty lines at the bottom of the control.
LinesTooMany := GetClientHeightInLines - GetLinesTillEnd(Value, LastLineReached);
if LinesTooMany > 0 then
begin
// scroll back upwards
ScrollPosition(Value, -LinesTooMany);
end;
FPosition := Value;
if Assigned(FOnPositionChanged) then
FOnPositionChanged(Self);
Invalidate;
// Set new scroll position.
if LastLineReached and (Value > 0) then
FScrollBarPosition := 100
else
FScrollBarPosition := Percent;
end;
// Update scrollbar position.
if FUpdateScrollBarPos then
begin
if FScrollBarVert.Position <> FScrollBarPosition then
begin
// Workaround for bug: http://bugs.freepascal.org/view.php?id=23815
{$IF DEFINED(LCLQT) and (LCL_FULLVERSION < 1010000)}
FScrollBarVert.OnScroll := nil;
FScrollBarVert.Position := FScrollBarPosition;
Application.ProcessMessages; // Skip message
FScrollBarVert.OnScroll := @ScrollBarVertScroll;
{$ELSE}
FScrollBarVert.Position := FScrollBarPosition;
{$ENDIF}
end;
end;
// else the scrollbar position will be updated in ScrollBarVertScroll
end;
procedure TViewerControl.SetEncoding(AEncoding: TViewerEncoding);
begin
if not (csDesigning in ComponentState) then
begin
if AEncoding = veAutoDetect then
FEncoding := DetectEncoding
else
FEncoding := AEncoding;
ReReadFile;
end
else
FEncoding := AEncoding;
end;
function TViewerControl.GetEncodingName: string;
begin
Result := ViewerEncodingsNames[FEncoding];
end;
procedure TViewerControl.SetEncodingName(AEncodingName: string);
var
i: TViewerEncoding;
begin
for i := Low(TViewerEncoding) to High(TViewerEncoding) do
if NormalizeEncoding(ViewerEncodingsNames[i]) = NormalizeEncoding(AEncodingName) then
begin
SetEncoding(i);
break;
end;
end;
function TViewerControl.GetClientHeightInLines(Whole: Boolean): Integer;
begin
if FTextHeight > 0 then
begin
if Whole then
Result := GetViewerRect.Height div FTextHeight
else
Result := Ceil(GetViewerRect.Height / FTextHeight);
end
else
Result := 0;
end;
function TViewerControl.GetLinesTillEnd(FromPosition: PtrInt;
out LastLineReached: Boolean): Integer;
var
iPos: PtrInt;
yIndex: Integer;
DataLength: PtrInt;
CharLenInBytes: Integer;
begin
Result := 0;
iPos := FromPosition;
for yIndex := 0 to GetClientHeightInLines - 1 do
begin
if iPos >= FHighLimit then
Break;
Inc(Result, 1);
case Mode of
vcmBin:
iPos := iPos + cBinWidth;
vcmHex,vcmDec:
iPos := iPos + FCustom.ValuesPerLine;
vcmText, vcmWrap, vcmBook:
CalcTextLineLength(iPos, FHighLimit, DataLength);
end;
end;
LastLineReached := (iPos >= FHighLimit);
if LastLineReached and (FViewerControlMode in [vcmText, vcmWrap, vcmBook]) then
begin
if (GetPrevCharAsAscii(FHighLimit, CharLenInBytes) = 10) then
Inc(Result);
end;
end;
procedure TViewerControl.SetShowCaret(AValue: Boolean);
begin
if FShowCaret <> AValue then
begin
FShowCaret:= AValue;
if HandleAllocated then
begin
if FShowCaret then
begin
LCLIntf.CreateCaret(Handle, 0, 2, FTextHeight);
LCLIntf.ShowCaret(Handle);
FCaretVisible:= True;
Invalidate;
end
else begin
FCaretVisible:= False;
LCLIntf.HideCaret(Handle);
LCLIntf.DestroyCaret(Handle);
end;
end;
end;
end;
procedure TViewerControl.SetCaretPos(AValue: PtrInt);
begin
if FCaretPos <> AValue then
begin
FCaretPos := AValue;
if FShowCaret then Invalidate;
end;
end;
function TViewerControl.GetPercent: Integer;
begin
if FHighLimit - FLowLimit > 0 then
Result := (Int64(FPosition - FLowLimit) * 100) div Int64(FHighLimit - FLowLimit)
else
Result := 0;
end;
procedure TViewerControl.SetPercent(const AValue: Integer);
begin
if FHighLimit - FLowLimit > 0 then
Position := Int64(AValue) * (Int64(FHighLimit - FLowLimit) div 100) + FLowLimit
else
Position := 0;
end;
procedure TViewerControl.SetBlockBegin(const AValue: PtrInt);
begin
if (AValue >= FLowLimit) and (AValue < FHighLimit) then
begin
if FBlockEnd < AValue then
FBlockEnd := AValue;
FBlockBeg := AValue;
Invalidate;
end;
end;
procedure TViewerControl.SetBlockEnd(const AValue: PtrInt);
begin
if (AValue >= FLowLimit) and (AValue < FHighLimit) then
begin
if FBlockBeg > AValue then
FBlockBeg := AValue;
FBlockEnd := AValue;
Invalidate;
end;
end;
procedure TViewerControl.OutText(x, y: Integer; const sText: String;
StartPos: PtrInt; DataLength: Integer);
var
pBegLine, pEndLine: PtrInt;
iBegDrawIndex, iEndDrawIndex: PtrInt;
begin
pBegLine := StartPos;
pEndLine := pBegLine + DataLength;
Canvas.Font.Color := Font.Color;
if FShowCaret and (FCaretPos >= pBegLine) and (FCaretPos <= pEndLine) then
begin
FCaretPoint.Y:= Y;
FCaretPoint.X:= X + Canvas.TextWidth(GetText(StartPos, FCaretPos - pBegLine, 0));
end;
// Out of selection, draw normal
if ((FBlockEnd - FBlockBeg) = 0) or ((FBlockBeg < pBegLine) and (FBlockEnd < pBegLine)) or // before
((FBlockBeg > pEndLine) and (FBlockEnd > pEndLine)) then // after
begin
Canvas.TextOut(x, y, sText);
Exit;
end;
// Get selection start
if (FBlockBeg <= pBegLine) then
iBegDrawIndex := pBegLine
else
iBegDrawIndex := FBlockBeg;
// Get selection end
if (FBlockEnd < pEndLine) then
iEndDrawIndex := FBlockEnd
else
iEndDrawIndex := pEndLine;
// Text after selection.
if pEndLine - iEndDrawIndex > 0 then
Canvas.TextOut(x, y, sText);
// Text before selection + selected text
Canvas.Brush.Color := clHighlight;
Canvas.Font.Color := clHighlightText;
Canvas.TextOut(X, Y, GetText(StartPos, iEndDrawIndex - pBegLine, 0));
// Restore previous canvas settings
Canvas.Brush.Color := Color;
Canvas.Font.Color := Font.Color;
// Text before selection
if iBegDrawIndex - pBegLine > 0 then
Canvas.TextOut(X, Y, GetText(StartPos, iBegDrawIndex - pBegLine, 0));
end;
procedure TViewerControl.OutCustom(x, y: Integer; const sText: String;
StartPos: PtrInt; DataLength: Integer);
var
sTmpText: String;
pBegLine, pEndLine: PtrInt;
iBegDrawIndex, iEndDrawIndex: PtrInt;
begin
pBegLine := StartPos;
pEndLine := pBegLine + DataLength;
Canvas.Font.Color := Font.Color;
if FShowCaret and (FCaretPos >= pBegLine) and (FCaretPos <= pEndLine) then
begin
FCaretPoint.Y:= Y;
FCaretPoint.X:= X + Canvas.TextWidth(Copy(sText, 1, FCustom.StartAscii + (FCaretPos - pBegLine)));
end;
// Out of selection, draw normal
if ((FBlockEnd - FBlockBeg) = 0) or ((FBlockBeg < pBegLine) and (FBlockEnd <= pBegLine)) or // before
((FBlockBeg > pEndLine) and (FBlockEnd > pEndLine)) then // after
begin
// Offset + hex part + space between hex and ascii
sTmpText:= Copy(sText, 1, FCustom.EndOfs) + ' ';
Canvas.TextOut(x, y, sTmpText);
x := x + Canvas.TextWidth(sTmpText);
// Ascii part
sTmpText := Copy(sText, 1 + FCustom.StartAscii, MaxInt);
Canvas.TextOut(x, y, sTmpText);
Exit;
end;
// Get selection start
if (FBlockBeg <= pBegLine) then
iBegDrawIndex := pBegLine
else begin
iBegDrawIndex := FBlockBeg;
end;
// Get selection end
if (FBlockEnd < pEndLine) then
iEndDrawIndex := FBlockEnd
else begin
iEndDrawIndex := pEndLine;
end;
// Text after selection (hex part)
if pEndLine - iEndDrawIndex > 0 then
begin
sTmpText := Copy(sText, 1, FCustom.StartOfs + (pEndLine - pBegLine) * (FCustom.MaxValueDigits + FCustom.SpaceCount));
Canvas.TextOut(x, y, sTmpText);
end;
// Text before selection + selected text (hex part)
sTmpText := Copy(sText, 1, FCustom.StartOfs + (iEndDrawIndex - pBegLine) * (FCustom.MaxValueDigits + FCustom.SpaceCount) - 1);
Canvas.Brush.Color := clHighlight;
Canvas.Font.Color := clHighlightText;
Canvas.TextOut(x, y, sTmpText);
// Restore previous canvas settings
Canvas.Brush.Color := Color;
Canvas.Font.Color := Font.Color;
// Offset + text before selection (hex part)
sTmpText := Copy(sText, 1, FCustom.StartOfs + (iBegDrawIndex - pBegLine) * (FCustom.MaxValueDigits + FCustom.SpaceCount));
Canvas.TextOut(x, y, sTmpText);
// Offset + hex part + space between hex and ascii
sTmpText:= Copy(sText, 1, FCustom.EndOfs) + ' ';
x := x + Canvas.TextWidth(sTmpText);
// Text after selection (ascii part)
if pEndLine - iEndDrawIndex > 0 then
begin
sTmpText := Copy(sText, FCustom.StartAscii + 1, MaxInt);
Canvas.TextOut(x, y, sTmpText);
end;
// Text before selection + selected text (ascii part)
if (iEndDrawIndex - pBegLine) = FCustom.ValuesPerLine then
sTmpText := Copy(sText, 1 + FCustom.StartAscii, MaxInt)
else begin
sTmpText := UTF8Copy(sText, 1 + FCustom.StartAscii, iEndDrawIndex - pBegLine);
end;
Canvas.Brush.Color := clHighlight;
Canvas.Font.Color := clHighlightText;
Canvas.TextOut(x, y, sTmpText);
// Restore background color
Canvas.Brush.Color := Color;
Canvas.Font.Color := Font.Color;
// Text before selection (ascii part)
if iBegDrawIndex - pBegLine > 0 then
begin
sTmpText := UTF8Copy(sText, 1 + FCustom.StartAscii, iBegDrawIndex - pBegLine);
Canvas.TextOut(x, y, sTmpText);
end;
end;
procedure TViewerControl.OutBin(x, y: Integer; const sText: String;
StartPos: PtrInt; DataLength: Integer);
var
pBegLine, pEndLine: PtrInt;
iBegDrawIndex, iEndDrawIndex: PtrInt;
begin
pBegLine := StartPos;
pEndLine := pBegLine + DataLength;
Canvas.Font.Color := Font.Color;
if FShowCaret and (FCaretPos >= pBegLine) and (FCaretPos <= pEndLine) then
begin
FCaretPoint.Y:= Y;
FCaretPoint.X:= X + Canvas.TextWidth(Copy(sText, 1, FCaretPos - pBegLine));
end;
// Out of selection, draw normal
if ((FBlockEnd - FBlockBeg) = 0) or ((FBlockBeg < pBegLine) and (FBlockEnd < pBegLine)) or // before
((FBlockBeg > pEndLine) and (FBlockEnd > pEndLine)) then //after
begin
Canvas.TextOut(x, y, sText);
Exit;
end;
// Get selection start/end.
if (FBlockBeg <= pBegLine) then
iBegDrawIndex := pBegLine
else begin
iBegDrawIndex := FBlockBeg;
end;
if (FBlockEnd < pEndLine) then
iEndDrawIndex := FBlockEnd
else begin
iEndDrawIndex := pEndLine;
end;
// Text after selection.
if pEndLine - iEndDrawIndex > 0 then
Canvas.TextOut(x, y, sText);
// Text before selection + selected text
Canvas.Brush.Color := clHighlight;
Canvas.Font.Color := clHighlightText;
// Whole line selected
if (iEndDrawIndex - pBegLine) = DataLength then
Canvas.TextOut(X, Y, sText)
else begin
Canvas.TextOut(X, Y, UTF8Copy(sText, 1, iEndDrawIndex - pBegLine));
end;
// Restore previous canvas settings
Canvas.Brush.Color := Color;
Canvas.Font.Color := Font.Color;
// Text before selection
if iBegDrawIndex - pBegLine > 0 then
Canvas.TextOut(X, Y, UTF8Copy(sText, 1, iBegDrawIndex - pBegLine));
end;
procedure TViewerControl.AddLineOffset(const iOffset: PtrInt);
begin
FLineList.Add(iOffset);
end;
procedure TViewerControl.KeyDown(var Key: word; Shift: TShiftState);
var
CharLenInBytes: Integer;
begin
if Shift = [] then
begin
case Key of
VK_DOWN:
begin
Key := 0;
Scroll(1);
end;
VK_UP:
begin
Key := 0;
Scroll(-1);
end;
VK_RIGHT:
begin
Key := 0;
HScroll(1);
end;
VK_LEFT:
begin
Key := 0;
HScroll(-1);
end;
VK_HOME:
begin
Key := 0;
CaretGoHome;
end;
VK_END:
begin
Key := 0;
CaretGoEnd;
end;
VK_PRIOR:
begin
Key := 0;
PageUp;
end;
VK_NEXT:
begin
Key := 0;
PageDown;
end;
else
inherited KeyDown(Key, Shift);
end;
end
else if Shift = [ssCtrl] then
begin
case Key of
VK_HOME:
begin
Key := 0;
CaretPos := FLowLimit;
MakeVisible(FCaretPos)
end;
VK_END:
begin
Key := 0;
CaretPos := FHighLimit;
MakeVisible(FCaretPos);
end;
else
inherited KeyDown(Key, Shift);
end;
end
else
inherited KeyDown(Key, Shift);
end;
function TViewerControl.FindAsciiSetForward(aPosition, aMaxBytes: PtrInt;
const AsciiChars: String;
bFindNotIn: Boolean): PtrInt;
var
i: Integer;
found: Boolean;
u: Cardinal;
CharLenInBytes: Integer;
begin
Result := -1;
while aMaxBytes > 0 do
begin
u := GetNextCharAsAscii(aPosition, CharLenInBytes);
if CharLenInBytes = 0 then
Exit;
if not bFindNotIn then
begin
for i := 1 to Length(AsciiChars) do
if u = ord(AsciiChars[i]) then
Exit(aPosition);
end
else
begin
found := False;
for i := 1 to Length(AsciiChars) do
if u = ord(AsciiChars[i]) then
begin
found := True;
break;
end;
if not found then
Exit(aPosition);
end;
Inc(aPosition, CharLenInBytes);
Dec(aMaxBytes, CharLenInBytes);
end;
end;
function TViewerControl.FindAsciiSetBackward(aPosition, aMaxBytes: PtrInt;
const AsciiChars: String;
bFindNotIn: Boolean): PtrInt;
var
i: Integer;
found: Boolean;
u: Cardinal;
CharLenInBytes: Integer;
begin
Result := -1;
while aMaxBytes > 0 do
begin
u := GetPrevCharAsAscii(aPosition, CharLenInBytes);
if CharLenInBytes = 0 then
Exit;
if not bFindNotIn then
begin
for i := 1 to Length(AsciiChars) do
if u = ord(AsciiChars[i]) then
Exit(aPosition);
end
else
begin
found := False;
for i := 1 to Length(AsciiChars) do
if u = ord(AsciiChars[i]) then
begin
found := True;
break;
end;
if not found then
Exit(aPosition);
end;
Dec(aPosition, CharLenInBytes);
Dec(aMaxBytes, CharLenInBytes);
end;
end;
procedure TViewerControl.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
var
LineBegin, LineEnd: PtrInt;
ClickPos: PtrInt;
CharSide: TCharSide;
CharLenInBytes: Integer;
begin
inherited;
SetFocus;
if not IsFileOpen then
Exit;
case Button of
mbLeft:
begin
if Shift * [ssDouble, ssTriple] = [] then
begin
// Single click.
ClickPos := XYPos2Adr(x, y, CharSide);
if ClickPos <> -1 then
begin
FBlockBeg := ClickPos;
FBlockEnd := ClickPos;
FCaretPos := ClickPos;
FMouseBlockBeg := ClickPos;
FMouseBlockSide := CharSide;
FSelecting := True;
if CharSide in [csRight, csAfter] then
begin
if FViewerControlMode in [vcmDec, vcmHex, vcmBin] then
CharLenInBytes := 1
else begin
GetNextCharAsAscii(FCaretPos, CharLenInBytes);
end;
FCaretPos := FCaretPos + CharLenInBytes;
end;
Invalidate;
end
else
FSelecting := False;
end
else // if double click or triple click
begin
FSelecting := False;
LineBegin := GetStartOfLine(FMouseBlockBeg);
LineEnd := GetEndOfLine(FMouseBlockBeg);
if ssDouble in Shift then
begin
// Select word with double-click.
FBlockBeg := FindAsciiSetBackward(FMouseBlockBeg,
FMouseBlockBeg - LineBegin, sNonCharacter, False);
FBlockEnd := FindAsciiSetForward(FMouseBlockBeg,
LineEnd - FMouseBlockBeg, sNonCharacter, False);
end
else if ssTriple in Shift then
begin
// Select line with triple-click.
FBlockBeg := FindAsciiSetForward(LineBegin,
LineEnd - LineBegin, sWhiteSpace, True);
FBlockEnd := FindAsciiSetBackward(LineEnd,
LineEnd - LineBegin, sWhiteSpace, True);
end;
if FBlockBeg = -1 then
FBlockBeg := LineBegin;
if FBlockEnd = -1 then
FBlockEnd := LineEnd;
if FBlockBeg > FBlockEnd then
FBlockEnd := FBlockBeg;
if FAutoCopy then
CopyToClipboard;
Invalidate;
end;
end; // mbLeft
end; // case
end;
procedure TViewerControl.MouseMove(Shift: TShiftState; X, Y: Integer);
procedure MoveOneChar(var aPosition: PtrInt);
var
CharLenInBytes: Integer;
begin
if FViewerControlMode in [vcmDec, vcmHex, vcmBin] then
CharLenInBytes := 1
else begin
GetNextCharAsAscii(aPosition, CharLenInBytes);
end;
aPosition := aPosition + CharLenInBytes;
end;
procedure MoveOneCharByMouseSide(var aPosition: PtrInt);
begin
if FMouseBlockSide in [csRight, csAfter] then
MoveOneChar(aPosition);
end;
var
ClickPos: PtrInt;
CharSide: TCharSide;
begin
inherited;
if FSelecting then
begin
if y < FTextHeight then
Scroll(-3)
else if y > ClientHeight - FTextHeight then
Scroll(3);
ClickPos := XYPos2Adr(x, y, CharSide);
if ClickPos <> -1 then
begin
if ClickPos < FMouseBlockBeg then
begin
// Got a new beginning.
FBlockBeg := ClickPos;
FBlockEnd := FMouseBlockBeg;
// Move end beyond last character.
MoveOneCharByMouseSide(FBlockEnd);
// When selecting from right to left, the current selected side must be
// either csLeft or csBefore, otherwise current position is not included.
if not (CharSide in [csLeft, csBefore]) then
begin
// Current position should not be included in selection.
// Move beginning after first character.
MoveOneChar(FBlockBeg);
end;
FCaretPos:= FBlockBeg;
end
else if ClickPos > FMouseBlockBeg then
begin
// Got a new end.
FBlockBeg := FMouseBlockBeg;
FBlockEnd := ClickPos;
// Move beginning after first character.
MoveOneCharByMouseSide(FBlockBeg);
// When selecting from left to right, the current selected side must be
// either csRight or csAfter, otherwise current position is not included.
if CharSide in [csRight, csAfter] then
begin
// Current position should be included in selection.
// Move end beyond last character.
MoveOneChar(FBlockEnd);
end;
FCaretPos:= FBlockEnd;
end
else if FMouseBlockSide <> CharSide then
begin
// Same position but changed side of the character.
FBlockBeg := FMouseBlockBeg;
FBlockEnd := FMouseBlockBeg;
if ((FMouseBlockSide in [csBefore, csLeft]) and
(CharSide in [csRight, csAfter])) or
((FMouseBlockSide in [csRight, csAfter]) and
(CharSide in [csBefore, csLeft])) then
begin
// Move end beyond last character.
MoveOneChar(FBlockEnd);
end;
FCaretPos:= FBlockEnd;
end
else
begin
FBlockBeg := FMouseBlockBeg;
FBlockEnd := FMouseBlockBeg;
end;
Invalidate;
end;
end;
end;
procedure TViewerControl.MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
inherited;
if FSelecting and (Button = mbLeft) and (Shift * [ssDouble, ssTriple] = []) then
begin
if FAutoCopy then
CopyToClipboard;
FSelecting := False;
end;
end;
function TViewerControl.DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
Result := inherited;
if not Result then
Result := Scroll(Mouse.WheelScrollLines);
end;
function TViewerControl.DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
Result := inherited;
if not Result then
Result := Scroll(-Mouse.WheelScrollLines);
end;
function TViewerControl.DoMouseWheelLeft(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
Result:= inherited DoMouseWheelLeft(Shift, MousePos);
if not Result then
Result := HScroll(-Mouse.WheelScrollLines);
end;
function TViewerControl.DoMouseWheelRight(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
Result:= inherited DoMouseWheelRight(Shift, MousePos);
if not Result then
Result := HScroll(Mouse.WheelScrollLines);
end;
procedure TViewerControl.DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy; const AXProportion, AYProportion: Double);
begin
FScrollBarVert.Width := LCLIntf.GetSystemMetrics(SM_CYVSCROLL);
FScrollBarHorz.Height := LCLIntf.GetSystemMetrics(SM_CYHSCROLL);
inherited DoAutoAdjustLayout(AMode, AXProportion, AYProportion);
end;
function TViewerControl.XYPos2Adr(x, y: Integer; out CharSide: TCharSide): PtrInt;
var
yIndex: Integer;
StartLine, EndLine: PtrInt;
function XYPos2AdrBin: PtrInt;
var
I, J, L: Integer;
charWidth: Integer;
textWidth: Integer;
tmpPosition: PtrInt;
s, ss, sText: String;
InvalidCharLen: Integer;
begin
J:= 1;
ss := EmptyStr;
tmpPosition := StartLine;
sText := TransformBin(tmpPosition, EndLine);
L:= Length(sText);
for I := 1 to L do
begin
charWidth:= SafeUTF8NextCharLen(PByte(@sText[J]), (L - J) + 1, InvalidCharLen);
s:= Copy(sText, J, charWidth);
Inc(J, charWidth);
ss := ss + s;
textWidth := Canvas.TextWidth(ss);
if textWidth > x then
begin
charWidth := Canvas.TextWidth(s);
if textWidth - charWidth div 2 > x then
CharSide := csLeft
else
CharSide := csRight;
Exit(StartLine + I - 1); // -1 because we count from 1
end;
end;
CharSide := csBefore;
Result := EndLine;
end;
function XYPos2AdrCustom: PtrInt;
// | offset part | custom part | native part |
// | 0000AAAA: | FF AA CC AE | djfjks |
var
I, J, L: Integer;
charWidth: Integer;
textWidth: Integer;
tmpPosition: PtrInt;
InvalidCharLen: Integer;
ss, sText, sPartialText: String;
begin
tmpPosition := StartLine;
sText := TransformCustom(tmpPosition, EndLine);
if sText = '' then Exit;
// Clicked on offset part
ss := Copy(sText, 1, FCustom.StartOfs);
textWidth := Canvas.TextWidth(ss);
if textWidth > x then
begin
CharSide := csBefore;
Exit(StartLine);
end;
// Clicked on custom part
for I := 0 to FCustom.ValuesPerLine - 1 do
begin
sPartialText := Copy(sText, 1 + FCustom.StartOfs + I * (FCustom.MaxValueDigits + FCustom.SpaceCount), FCustom.MaxValueDigits);
ss := ss + sPartialText;
textWidth := Canvas.TextWidth(ss);
if textWidth > x then
begin
// Check if we're not after end of data.
if StartLine + I >= EndLine then
begin
CharSide := csBefore;
Exit(EndLine);
end;
charWidth := Canvas.TextWidth(sPartialText);
if textWidth - charWidth div 2 > x then
CharSide := csLeft
else
CharSide := csRight;
Exit(StartLine + I);
end;
// Space after hex number.
ss := ss + string(sText[1 + FCustom.StartOfs + I * (FCustom.MaxValueDigits + 1) + FCustom.MaxValueDigits]);
textWidth := Canvas.TextWidth(ss);
if textWidth > x then
begin
CharSide := csAfter;
Exit(StartLine + I);
end;
end;
// Clicked between hex and ascii.
sPartialText := Copy(sText, 1 + FCustom.StartOfs, FCustom.StartAscii - FCustom.EndOfs);
ss := ss + sPartialText;
textWidth := Canvas.TextWidth(ss);
if textWidth > x then
begin
Exit(-1); // No position.
end;
// Clicked on ascii part.
L:= Length(sText);
J:= 1 + FCustom.StartAscii;
for I := 0 to FCustom.ValuesPerLine - 1 do
begin
charWidth := SafeUTF8NextCharLen(PByte(@sText[J]), (L - J) + 1, InvalidCharLen);
sPartialText := Copy(sText, J, charWidth);
Inc(J, charWidth);
ss := ss + sPartialText;
textWidth := Canvas.TextWidth(ss);
if textWidth > x then
begin
// Check if we're not after end of data.
if StartLine + I >= EndLine then
begin
CharSide := csBefore;
Exit(EndLine);
end;
charWidth := Canvas.TextWidth(sPartialText);
if textWidth - charWidth div 2 > x then
CharSide := csLeft
else
CharSide := csRight;
Exit(StartLine + I);
end;
end;
CharSide := csBefore;
Result := EndLine;
end;
function XYPos2AdrText: PtrInt;
var
i: PtrInt;
Dos: Boolean;
charWidth: Integer;
textWidth: Integer;
len: Integer = 0;
CharLenInBytes: Integer;
s: String;
ss: String;
begin
ss := '';
i := StartLine;
Dos:= FEncoding in ViewerEncodingOem;
while i < EndLine do
begin
s := GetNextCharAsUtf8(i, CharLenInBytes);
if CharLenInBytes = 0 then
Break;
// Check if the conversion to UTF-8 was successful.
if Length(s) > 0 then
begin
if s = #9 then
begin
s := StringOfChar(' ', FTabSpaces - len mod FTabSpaces);
len := len + (FTabSpaces - len mod FTabSpaces);
end
else
Inc(len); // Assume there is one character after conversion
// (otherwise use Inc(len, UTF8Length(s))).
if (Mode = vcmText) and (len <= FHPosition) then
begin
i := i + CharLenInBytes;
Continue;
end;
if (CharLenInBytes = 1) and (s[1] < ' ') then
begin
if Dos then
s := ASCII_TABLE[Ord(s[1])]
else
s := ' ';
end;
ss := ss + s;
textWidth := Canvas.TextWidth(ss);
if textWidth > x then
begin
charWidth := Canvas.TextWidth(s);
if textWidth - charWidth div 2 > x then
CharSide := csLeft
else
CharSide := csRight;
Exit(i);
end;
end;
i := i + CharLenInBytes;
end;
CharSide := csBefore;
Result := EndLine;
end;
begin
if FLineList.Count = 0 then
Exit(-1);
if (x < FLeftMargin) then
x := 0
else begin
x := x - FLeftMargin;
end;
yIndex := y div FTextHeight;
if yIndex >= FLineList.Count then
yIndex := FLineList.Count - 1;
if yIndex < 0 then
yIndex := 0;
// Get position of first character of the line.
StartLine := FLineList.Items[yIndex];
// Get position of last character of the line.
EndLine := GetEndOfLine(StartLine);
if (x = 0) and ((Mode <> vcmText) or (FHPosition = 0)) then
begin
CharSide := csBefore;
Exit(StartLine);
end;
case Mode of
vcmBin:
Result := XYPos2AdrBin;
vcmHex,vcmDec:
Result := XYPos2AdrCustom; // XYPos2AdrHex;
vcmText, vcmWrap, vcmBook:
Result := XYPos2AdrText;
else
raise Exception.Create('Invalid viewer mode');
end;
end;
procedure TViewerControl.SelectAll;
begin
SelectText(FLowLimit, FHighLimit);
end;
procedure TViewerControl.SelectText(AStart, AEnd: PtrInt);
begin
if AStart < FLowLimit then
AStart := FLowLimit;
if AEnd > FHighLimit then
AEnd := FHighLimit;
if AStart <= AEnd then
begin
FBlockBeg := AStart;
FBlockEnd := AEnd;
Invalidate;
end;
end;
procedure TViewerControl.CopyToClipboard;
var
sText, utf8Text: string;
begin
if (FBlockEnd - FBlockBeg) <= 0 then
Exit;
if (FBlockEnd - FBlockBeg) > 1024 * 1024 then // Max 1 MB to clipboard
Exit;
SetString(sText, GetDataAdr + FBlockBeg, FBlockEnd - FBlockBeg);
utf8Text := ConvertToUTF8(sText);
{$IFDEF LCLGTK2}
// Workaround for Lazarus bug #0021453. LCL adds trailing zero to clipboard in Clipboard.AsText.
Clipboard.Clear;
Clipboard.AddFormat(PredefinedClipboardFormat(pcfText), utf8Text[1], Length(utf8Text));
{$ELSE}
Clipboard.AsText := utf8Text;
{$ENDIF}
end;
procedure TViewerControl.CopyToClipboardF;
var
s,sText, utf8Text: string;
len: Integer;
begin
len:=FBlockEnd-FBlockBeg;
if len=0 then exit;
sText:=TransformCustomBlock(FBlockBeg,len,False,False,s);
utf8Text := ConvertToUTF8(sText);
{$IFDEF LCLGTK2}
// Workaround for Lazarus bug #0021453. LCL adds trailing zero to clipboard in Clipboard.AsText.
Clipboard.Clear;
Clipboard.AddFormat(PredefinedClipboardFormat(pcfText), utf8Text[1], Length(utf8Text));
{$ELSE}
Clipboard.AsText := utf8Text;
{$ENDIF}
end;
function TViewerControl.Selection: String;
const
MAX_LEN = 512;
var
sText: String;
AIndex: PtrInt;
ALength: PtrInt;
CharLenInBytes: Integer;
begin
if (FBlockEnd - FBlockBeg) <= 0 then
Exit(EmptyStr);
ALength:= FBlockEnd - FBlockBeg;
if ALength <= MAX_LEN then
begin
SetString(sText, GetDataAdr + FBlockBeg, ALength);
Result := ConvertToUTF8(sText);
end
else begin
Result:= EmptyStr;
AIndex:= FBlockBeg;
ALength:= AIndex + MAX_LEN;
while AIndex < ALength do
begin
sText := GetNextCharAsUtf8(AIndex, CharLenInBytes);
if CharLenInBytes = 0 then
Break;
Result:= Result + sText;
AIndex:= AIndex + CharLenInBytes;
end;
end;
end;
function TViewerControl.GetNextCharAsAscii(const iPosition: PtrInt; out CharLenInBytes: Integer): Cardinal;
var
u1, u2: Word;
InvalidCharLen: Integer;
begin
Result := 0;
case FEncoding of
veUtf8, veUtf8bom:
begin
if iPosition < FHighLimit then
begin
CharLenInBytes := SafeUTF8NextCharLen(GetDataAdr + iPosition,
FHighLimit - iPosition,
InvalidCharLen);
// It's enough to only return Ascii.
if CharLenInBytes = 1 then
Result := PByte(GetDataAdr)[iPosition];
// Full conversion:
// Result := UTF8CodepointToUnicode(PAnsiChar(GetDataAdr + iPosition), CharLenInBytes);
end
else
CharLenInBytes := 0;
end;
veAnsi, veOem,
veCp1250..veCp874,
veIso88591,
veIso88592,
veKoi8r,
veKoi8u,
veKoi8ru:
if iPosition < FHighLimit then
begin
Result := PByte(GetDataAdr)[iPosition];
CharLenInBytes := 1;
end
else
CharLenInBytes := 0;
veUcs2be:
if iPosition + SizeOf(Word) - 1 < FHighLimit then
begin
Result := BEtoN(PWord(GetDataAdr + iPosition)[0]);
CharLenInBytes := SizeOf(Word);
end
else
CharLenInBytes := 0;
veUcs2le:
if iPosition + SizeOf(Word) - 1 < FHighLimit then
begin
Result := LEtoN(PWord(GetDataAdr + iPosition)[0]);
CharLenInBytes := SizeOf(Word);
end
else
CharLenInBytes := 0;
veUtf16be:
if iPosition + SizeOf(Word) - 1 < FHighLimit then
begin
u1 := BEtoN(PWord(GetDataAdr + iPosition)[0]);
CharLenInBytes := UTF16CharacterLength(@u1);
if CharLenInBytes = 1 then
begin
Result := u1;
end
else if iPosition + SizeOf(Word) * CharLenInBytes - 1 < FHighLimit then
begin
u2 := BEtoN(PWord(GetDataAdr + iPosition)[1]);
Result := utf16PairToUnicode(u1, u2);
end;
CharLenInBytes := CharLenInBytes * SizeOf(Word);
end
else
CharLenInBytes := 0;
veUtf16le:
if iPosition + SizeOf(Word) - 1 < FHighLimit then
begin
u1 := LEtoN(PWord(GetDataAdr + iPosition)[0]);
CharLenInBytes := UTF16CharacterLength(@u1);
if CharLenInBytes = 1 then
begin
Result := u1;
end
else if iPosition + SizeOf(Word) * CharLenInBytes - 1 < FHighLimit then
begin
u2 := LEtoN(PWord(GetDataAdr + iPosition)[1]);
Result := utf16PairToUnicode(u1, u2);
end
else
CharLenInBytes := 0;
CharLenInBytes := CharLenInBytes * SizeOf(Word);
end
else
CharLenInBytes := 0;
veUtf32be:
if iPosition + SizeOf(LongWord) - 1 < FHighLimit then
begin
Result := BEtoN(PLongWord(GetDataAdr + iPosition)[0]);
CharLenInBytes := SizeOf(LongWord);
end
else
CharLenInBytes := 0;
veUtf32le:
if iPosition + SizeOf(LongWord) - 1 < FHighLimit then
begin
Result := LEtoN(PLongWord(GetDataAdr + iPosition)[0]);
CharLenInBytes := SizeOf(LongWord);
end
else
CharLenInBytes := 0;
veCp932, // Unsupported variable-width encodings
veCp936, // TODO: Add cp932, cp936, cp949, cp950 encoding support
veCp949,
veCp950:
if iPosition < FHighLimit then
begin
Result := PByte(GetDataAdr)[iPosition];
CharLenInBytes := 1;
end
else
CharLenInBytes := 0;
else
raise Exception.Create('Unsupported viewer encoding');
end;
end;
function TViewerControl.GetPrevCharAsAscii(const iPosition: PtrInt; out CharLenInBytes: Integer): Cardinal;
var
u1, u2: Word;
InvalidCharLen: Integer;
begin
Result := 0;
case FEncoding of
veUtf8, veUtf8bom:
begin
if iPosition > FLowLimit then
begin
CharLenInBytes := SafeUTF8PrevCharLen(GetDataAdr + iPosition,
iPosition - FLowLimit,
InvalidCharLen);
// It's enough to only return Ascii.
if CharLenInBytes = 1 then
Result := PByte(GetDataAdr)[iPosition - 1];
// Full conversion:
// Result := UTF8CodepointToUnicode(PAnsiChar(GetDataAdr + iPosition - CharLenInBytes), CharLenInBytes);
end
else
CharLenInBytes := 0;
end;
veAnsi, veOem,
veCp1250..veCp874,
veIso88591,
veIso88592,
veKoi8r,
veKoi8u,
veKoi8ru:
if iPosition > FLowLimit then
begin
Result := PByte(GetDataAdr + iPosition)[-1];
CharLenInBytes := 1;
end
else
CharLenInBytes := 0;
veUcs2be:
if iPosition >= FLowLimit + SizeOf(Word) then
begin
Result := BEtoN(PWord(GetDataAdr + iPosition)[-1]);
CharLenInBytes := SizeOf(Word);
end
else
CharLenInBytes := 0;
veUcs2le:
if iPosition >= FLowLimit + SizeOf(Word) then
begin
Result := LEtoN(PWord(GetDataAdr + iPosition)[-1]);
CharLenInBytes := SizeOf(Word);
end
else
CharLenInBytes := 0;
veUtf16be:
if iPosition >= FLowLimit + SizeOf(Word) then
begin
u1 := BEtoN(PWord(GetDataAdr + iPosition)[-1]);
CharLenInBytes := UTF16CharacterLength(@u1);
if CharLenInBytes = 1 then
begin
Result := u1;
end
else if iPosition >= FLowLimit + SizeOf(Word) * CharLenInBytes then
begin
u2 := BEtoN(PWord(GetDataAdr + iPosition)[-2]);
// u2 is the first, u1 is the second value of the pair
Result := utf16PairToUnicode(u2, u1);
end;
CharLenInBytes := CharLenInBytes * SizeOf(Word);
end
else
CharLenInBytes := 0;
veUtf16le:
if iPosition >= FLowLimit + SizeOf(Word) then
begin
u1 := LEtoN(PWord(GetDataAdr + iPosition)[-1]);
CharLenInBytes := UTF16CharacterLength(@u1);
if CharLenInBytes = 1 then
begin
Result := u1;
end
else if iPosition >= FLowLimit + SizeOf(Word) * CharLenInBytes then
begin
u2 := LEtoN(PWord(GetDataAdr + iPosition)[-2]);
// u2 is the first, u1 is the second value of the pair
Result := utf16PairToUnicode(u2, u1);
end;
CharLenInBytes := CharLenInBytes * SizeOf(Word);
end
else
CharLenInBytes := 0;
veUtf32be:
if iPosition >= FLowLimit + SizeOf(LongWord) then
begin
Result := BEtoN(PLongWord(GetDataAdr + iPosition)[-1]);
CharLenInBytes := SizeOf(LongWord);
end
else
CharLenInBytes := 0;
veUtf32le:
if iPosition >= FLowLimit + SizeOf(LongWord) then
begin
Result := LEtoN(PLongWord(GetDataAdr + iPosition)[-1]);
CharLenInBytes := SizeOf(LongWord);
end
else
CharLenInBytes := 0;
veCp932, // Unsupported variable-width encodings
veCp936, // TODO: Add cp932, cp936, cp949, cp950 encoding support
veCp949,
veCp950:
if iPosition > FLowLimit then
begin
Result := PByte(GetDataAdr + iPosition)[-1];
CharLenInBytes := 1;
end
else
CharLenInBytes := 0;
else
raise Exception.Create('Unsupported viewer encoding');
end;
end;
function TViewerControl.GetNextCharAsUtf8(const iPosition: PtrInt; out CharLenInBytes: Integer): String;
var
u1: Word;
s: string;
InvalidCharLen: Integer;
begin
Result := '';
case FEncoding of
veUtf8, veUtf8bom:
CharLenInBytes := SafeUTF8NextCharLen(GetDataAdr + iPosition,
FHighLimit - iPosition,
InvalidCharLen);
veAnsi, veOem,
veCp1250..veCp874,
veIso88591,
veIso88592,
veKoi8r,
veKoi8u,
veKoi8ru:
CharLenInBytes := 1;
veUcs2be, veUcs2le:
CharLenInBytes := 2;
veUtf16be:
if iPosition + SizeOf(Word) - 1 < FHighLimit then
begin
u1 := BEtoN(PWord(GetDataAdr + iPosition)[0]);
CharLenInBytes := UTF16CharacterLength(@u1) * SizeOf(Word);
end
else
CharLenInBytes := 0;
veUtf16le:
if iPosition + SizeOf(Word) - 1 < FHighLimit then
begin
u1 := LEtoN(PWord(GetDataAdr + iPosition)[0]);
CharLenInBytes := UTF16CharacterLength(@u1) * SizeOf(Word);
end
else
CharLenInBytes := 0;
veUtf32be, veUtf32le:
CharLenInBytes := 4;
veCp932, // Unsupported variable-width encodings
veCp936, // TODO: Add cp932, cp936, cp949, cp950 encoding support
veCp949,
veCp950:
CharLenInBytes := 1;
else
raise Exception.Create('Unsupported viewer encoding');
end;
if (CharLenInBytes > 0) and (iPosition + CharLenInBytes - 1 < FHighLimit) then
begin
SetString(s, GetDataAdr + iPosition, CharLenInBytes);
Result := ConvertToUTF8(s);
end
else
Result := '';
end;
function TViewerControl.ConvertToUTF8(const sText: AnsiString): String;
begin
if FEncoding = veAutoDetect then
FEncoding := DetectEncoding; // Force detect encoding.
case FEncoding of
veAutoDetect: ;
veAnsi:
Result := CeAnsiToUtf8(sText);
veOem:
Result := CeOemToUtf8(sText);
veUtf8, veUtf8bom:
Result := Utf8ReplaceBroken(sText);
veUtf16be:
Result := Utf16BEToUtf8(sText);
veUtf16le:
Result := Utf16LEToUtf8(sText);
veUtf32be:
Result := Utf32BEToUtf8(sText);
veUtf32le:
Result := Utf32LEToUtf8(sText);
else
Result := LConvEncoding.ConvertEncoding(sText,
ViewerEncodingsNames[FEncoding], EncodingUTF8);
end;
end;
function TViewerControl.ConvertFromUTF8(const sText: String): AnsiString;
begin
if FEncoding = veAutoDetect then
FEncoding := DetectEncoding; // Force detect encoding.
case FEncoding of
veAutoDetect: ;
veAnsi:
Result := CeUtf8ToAnsi(sText);
veOem:
Result := CeUtf8ToOem(sText);
veUtf8, veUtf8bom:
Result := sText;
veUtf16be:
Result := Utf8ToUtf16BE(sText);
veUtf16le:
Result := Utf8ToUtf16LE(sText);
veUtf32be:
Result := '';//Utf8ToUtf32BE(sText);
veUtf32le:
Result := '';//Utf8ToUtf32LE(sText);
else
Result := LConvEncoding.ConvertEncoding(sText,
EncodingUTF8, ViewerEncodingsNames[FEncoding]);
end;
end;
function TViewerControl.IsVisible(const aPosition: PtrInt): Boolean;
var
StartPos: PtrInt;
CharLenInBytes: Integer;
begin
if IsFileOpen and (FLineList.Count > 0) then
begin
FVisibleOffset:= 0;
StartPos:= GetStartOfLine(aPosition);
// Calculate horizontal offset in symbols
while (StartPos < aPosition) do
begin
GetNextCharAsAscii(StartPos, CharLenInBytes);
Inc(StartPos, CharLenInBytes);
Inc(FVisibleOffset);
end;
Result := (aPosition >= FLineList.Items[0]) and
(aPosition <= FLineList.Items[FLineList.Count - 1]) and
(FVisibleOffset >= FHPosition) and
(FVisibleOffset <= FHPosition + FTextWidth);
end
else
Result := False;
end;
procedure TViewerControl.MakeVisible(const aPosition: PtrInt);
var
Offset: Integer;
LastLine: Boolean;
begin
if not IsVisible(aPosition) then
begin
SetPosition(aPosition);
Offset:= GetLinesTillEnd(aPosition, LastLine);
if (Offset > 4) and (LastLine = False) then Scroll(-4);
Update;
if FViewerControlMode = vcmText then
begin
if (FVisibleOffset < FHPosition) or
(FVisibleOffset > FHPosition + FTextWidth) then
begin
SetHPosition(FVisibleOffset);
HScroll(-1);
end;
end;
end;
end;
procedure TViewerControl.ScrollBarVertScroll(Sender: TObject;
ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
FUpdateScrollBarPos := False;
case ScrollCode of
scLineUp: Scroll(-1);
scLineDown: Scroll(1);
scPageUp: PageUp;
scPageDown: PageDown;
scTop: GoHome;
scBottom: GoEnd;
scTrack,
scPosition:
begin
// This check helps avoiding loops if changing ScrollPos below
// triggers another scPosition message.
if (ScrollCode = scTrack) or (ScrollPos <> FScrollBarPosition) then
begin
if ScrollPos = 0 then
GoHome
else if ScrollPos = 100 then
GoEnd
else
Percent := ScrollPos;
end;
end;
scEndScroll:
begin
end;
end;
ScrollPos := FScrollBarPosition;
FUpdateScrollBarPos := True;
end;
procedure TViewerControl.ScrollBarHorzScroll(Sender: TObject;
ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
FUpdateScrollBarPos := False;
case ScrollCode of
scLineUp: HScroll(-1);
scLineDown: HScroll(1);
scPageUp: HPageUp;
scPageDown: HPageDown;
scTop: HGoHome;
scBottom: HGoEnd;
scTrack,
scPosition:
begin
// This check helps avoiding loops if changing ScrollPos below
// triggers another scPosition message.
if (ScrollCode = scTrack) or (ScrollPos <> FHScrollBarPosition) then
begin
if ScrollPos = 0 then
HGoHome
else if ScrollPos = 100 then
HGoEnd
else
HScroll((FHLowEnd - FTextWidth) * ScrollPos div 100 - FHPosition);
end;
end;
scEndScroll:
begin
end;
end;
ScrollPos := FHScrollBarPosition;
FUpdateScrollBarPos := True;
end;
procedure TViewerControl.UpdateScrollbars;
begin
FScrollBarVert.LargeChange := GetClientHeightInLines - 1;
case Mode of
vcmBin, vcmHex:
begin
//FScrollBarVert.PageSize :=
// ((FHighLimit div cHexWidth - GetClientHeightInLines) div 100);
end
else
FScrollBarVert.PageSize := 1;
end;
FScrollBarHorz.Visible:= (FViewerControlMode = vcmText);
end;
procedure TViewerControl.ViewerResize(Sender: TObject);
begin
UpdateScrollbars;
// Force recalculating position.
SetPosition(FPosition);
SetHPosition(FHPosition);
end;
procedure TViewerControl.ReReadFile;
begin
FBlockBeg := 0;
FBlockEnd := 0;
FBOMLength := GetBomLength;
UpdateLimits;
UpdateScrollbars;
Invalidate;
end;
function TViewerControl.IsFileOpen: Boolean;
begin
Result := Assigned(FMappedFile);
end;
function TViewerControl.DetectEncoding: TViewerEncoding;
var
DetectStringLength: Integer = 4096; // take first 4kB of the file to detect encoding
DetectString: String;
DetectedEncodingName: String;
Enc: TViewerEncoding;
begin
if IsFileOpen then
begin
// Default to Ansi in case encoding cannot be detected or is unsupported.
Result := veAnsi;
if FFileSize < DetectStringLength then
DetectStringLength := FFileSize;
SetString(DetectString, PAnsiChar(FMappedFile), DetectStringLength);
if Assigned(FOnGuessEncoding) then
DetectedEncodingName := FOnGuessEncoding(DetectString)
else
DetectedEncodingName := LConvEncoding.GuessEncoding(DetectString);
if DetectedEncodingName <> '' then
begin
DetectedEncodingName := NormalizeEncoding(DetectedEncodingName);
// Map UCS-2 to UTF-16.
if DetectedEncodingName = 'ucs2le' then
DetectedEncodingName := 'utf16le'
else if DetectedEncodingName = 'ucs2be' then
DetectedEncodingName := 'utf16be';
for Enc := Low(TViewerEncoding) to High(TViewerEncoding) do
begin
if NormalizeEncoding(ViewerEncodingsNames[Enc]) = DetectedEncodingName then
begin
Result := Enc;
break;
end;
end;
end;
end
else
Result := veAutoDetect;
end;
procedure TViewerControl.GetSupportedEncodings(List: TStrings);
var
Enc: TViewerEncoding;
begin
for Enc := Low(TViewerEncoding) to High(TViewerEncoding) do
List.Add(ViewerEncodingsNames[Enc]);
end;
function TViewerControl.GetBomLength: Integer;
begin
Result := 0;
case FEncoding of
veUtf8, veUtf8bom:
if (FFileSize >= 3) and
(PByte(FMappedFile)[0] = $EF) and
(PByte(FMappedFile)[1] = $BB) and
(PByte(FMappedFile)[2] = $BF) then
begin
Result := 3;
end;
veUcs2be, veUtf16be:
if (FFileSize >= 2) and
(PByte(FMappedFile)[0] = $FE) and
(PByte(FMappedFile)[1] = $FF) then
begin
Result := 2;
end;
veUcs2le, veUtf16le:
if (FFileSize >= 2) and
(PByte(FMappedFile)[0] = $FF) and
(PByte(FMappedFile)[1] = $FE) then
begin
Result := 2;
end;
veUtf32be:
if (FFileSize >= 4) and
(PByte(FMappedFile)[0] = $00) and
(PByte(FMappedFile)[1] = $00) and
(PByte(FMappedFile)[2] = $FE) and
(PByte(FMappedFile)[3] = $FF) then
begin
Result := 4;
end;
veUtf32le:
if (FFileSize >= 4) and
(PByte(FMappedFile)[0] = $00) and
(PByte(FMappedFile)[1] = $00) and
(PByte(FMappedFile)[2] = $FF) and
(PByte(FMappedFile)[3] = $FE) then
begin
Result := 4;
end;
end;
end;
procedure TViewerControl.UpdateLimits;
begin
if FEncoding = veAutoDetect then
FEncoding := DetectEncoding;
FBOMLength := GetBomLength;
case FViewerControlMode of
vcmText, vcmWrap, vcmBook:
begin
FLowLimit := 0;
FHighLimit := FFileSize - FBOMLength;
end;
else
begin
FLowLimit := 0;
FHighLimit := FFileSize;
end;
end;
end;
procedure TViewerControl.UpdateSelection;
procedure Check(var aPosition: PtrInt; Backwards: Boolean);
var
CharStart: Pointer;
begin
case FEncoding of
veUtf8, veUtf8bom:
begin
if not Backwards then
begin
CharStart := SafeUTF8NextCharStart(GetDataAdr + aPosition,
FHighLimit - aPosition);
if Assigned(CharStart) then
aPosition := CharStart - GetDataAdr
else
aPosition := 0;
end
else
begin
CharStart := SafeUTF8PrevCharEnd(GetDataAdr + aPosition,
aPosition - FLowLimit);
if Assigned(CharStart) then
aPosition := CharStart - GetDataAdr
else
aPosition := 0;
end;
end;
veAnsi, veOem,
veCp1250..veCp874,
veIso88591,
veIso88592,
veKoi8r,
veKoi8u,
veKoi8ru:
; // any position allowed
veUcs2be, veUcs2le:
aPosition := ((aPosition - FLowLimit) and not 1) + FLowLimit;
veUtf16be, veUtf16le:
// todo: check if not in the middle of utf-16 character
aPosition := ((aPosition - FLowLimit) and not 1) + FLowLimit;
veUtf32be, veUtf32le:
aPosition := ((aPosition - FLowLimit) and not 3) + FLowLimit;
veCp932, // Unsupported variable-width encodings
veCp936, // TODO: Add cp932, cp936, cp949, cp950 encoding support
veCp949,
veCp950:
;
else
raise Exception.Create('Unsupported viewer encoding');
end;
end;
begin
if (FBlockBeg < FLowLimit) or (FBlockBeg >= FHighLimit) or
(FBlockEnd < FLowLimit) or (FBlockEnd >= FHighLimit) then
begin
FBlockBeg := FLowLimit;
FBlockEnd := FLowLimit;
end
else
begin
case FViewerControlMode of
vcmText, vcmWrap, vcmBook:
begin
Check(FBlockBeg, False);
Check(FBlockEnd, True);
if (FBlockBeg < FLowLimit) or (FBlockBeg >= FHighLimit) or
(FBlockEnd < FLowLimit) or (FBlockEnd >= FHighLimit) or
(FBlockEnd < FBlockBeg) then
begin
FBlockBeg := FLowLimit;
FBlockEnd := FLowLimit;
end;
end;
// In non-text modes any selection is valid.
end;
end;
end;
function TViewerControl.FindUtf8Text(iStartPos: PtrInt; const sSearchText: String;
bCaseSensitive: Boolean; bSearchBackwards: Boolean): PtrInt;
var
SearchTextLength: Integer;
sSearchChars: array of String;
pCurrentAddr, pEndAddr: PtrInt;
i, charLen: Integer;
function sPos2(pAdr: PtrInt):Boolean;
var
curChr:String;
i, charLen: Integer;
begin
Result := False;
for i := 0 to SearchTextLength-1 do
begin
curChr:=GetNextCharAsUtf8(pAdr,charLen);
case bCaseSensitive of
False: if UTF8UpperCase(curChr) <> UTF8UpperCase(sSearchChars[i]) then Exit;
True : if curChr <> sSearchChars[i] then Exit;
end;
if charLen>0 then
pAdr:=pAdr+charLen
else
Inc(pAdr);
end;
Result:=True;
end;
begin
Result := PtrInt(-1);
SearchTextLength := UTF8Length(sSearchText);
if (SearchTextLength <= 0) then
Exit;
setLength(sSearchChars,SearchTextLength);
for i:=1 to SearchTextLength do
sSearchChars[i-1]:=UTF8Copy(sSearchText,i,1);
pCurrentAddr := iStartPos;
pEndAddr := FHighLimit - Length(ConvertFromUTF8(sSearchText));
if bSearchBackwards and (pCurrentAddr > pEndAddr) then
// Move to the first possible position for searching backwards.
pCurrentAddr := pEndAddr;
if (pEndAddr < 0) or (pCurrentAddr < 0) or (pCurrentAddr > pEndAddr) then
Exit;
while True do
begin
if (pCurrentAddr > pEndAddr) or (pCurrentAddr < 0) then
Exit;
if sPos2(pCurrentAddr) then
begin
Result := pCurrentAddr;
Exit;
end;
case bSearchBackwards of
False:
begin
GetNextCharAsUtf8(pCurrentAddr,charLen);
if charLen>0 then
pCurrentAddr:=pCurrentAddr+charLen
else
Inc(pCurrentAddr);
end;
True : Dec(pCurrentAddr);
end;
end;
end;
procedure TViewerControl.ResetEncoding;
begin
FEncoding:= veAutoDetect;
end;
procedure Register;
begin
RegisterComponents('SeksiCmd', [TViewerControl]);
end;
end.