lazarus-ccr/components/kcontrols/source/khexeditor.pas
sekelsenmat eb3ecee187 Initial commit of kcontrols
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@1732 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2011-07-04 06:24:41 +00:00

5119 lines
172 KiB
ObjectPascal
Executable File

{ @abstract(This unit contains the TKHexEditor component and all supporting classes)
@author(Tomas Krysl (tk@tkweb.eu))
@created(12 Oct 2005)
@lastmod(20 Jun 2010)
This unit provides a powerfull hexadecimal editor component @link(TKHexEditor)
with following major features:
<UL>
<LI><I>advanced editing capabilities</I></LI>
<LI><I>advanced rendering styles</I></LI>
<LI><I>clipboard operations</I></LI>
<LI><I>virtually unlimited undo/redo operations</I></LI>
<LI><I>key mapping functionality</I></LI>
<LI><I>fast search/replace function</I></LI>
<LI><I>print/preview function</I></LI>
</UL>
Copyright © 2006 Tomas Krysl (tk@@tkweb.eu)<BR><BR>
<B>License:</B><BR>
This code is distributed as a freeware. You are free to use it as part
of your application for any purpose including freeware, commercial and
shareware applications. The origin of this source code must not be
misrepresented; you must not claim your authorship. You may modify this code
solely for your own purpose. Please feel free to contact the author if you
think your changes might be useful for other users. You may distribute only
the original package. The author accepts no liability for any damage
that may result from using this code. }
unit KHexEditor;
{$include kcontrols.inc}
{$WEAKPACKAGEUNIT ON}
interface
uses
{$IFDEF FPC}
LCLType, LCLIntf, LMessages, LCLProc, LResources,
{$ELSE}
Windows, Messages,
{$ENDIF}
SysUtils, Classes, Graphics, Controls,
ExtCtrls, StdCtrls, Forms, KFunctions, KControls, KEditCommon;
resourcestring
{ @exclude }
sAddressText = 'Address area text';
{ @exclude }
sAddressBkGnd = 'Address area background';
{ @exclude }
sBkGnd = 'Editor background';
{ @exclude }
sDigitTextEven = 'Digit area even column';
{ @exclude }
sDigitTextOdd = 'Digit area odd column';
{ @exclude }
sDigitBkgnd = 'Digit area background';
{ @exclude }
sHorzLines = 'Horizontal lines';
{ @exclude }
sInactiveCaretBkGnd = 'Inactive caret background';
{ @exclude }
sInactiveCaretSelBkGnd = 'Selected inactive caret background';
{ @exclude }
sInactiveCaretSelText = 'Selected inactive caret text';
{ @exclude }
sInactiveCaretText = 'Inactive caret text';
{ @exclude }
sLinesHighLight = 'Lines highlight';
{ @exclude }
sSelBkGnd = 'Selection background';
{ @exclude }
sSelBkGndFocused = 'Focused selection background';
{ @exclude }
sSelText = 'Selection text';
{ @exclude }
sSelTextFocused = 'Focused selection text';
{ @exclude }
sSeparators = 'Area separating lines';
{ @exclude }
sTextText = 'Text area text';
{ @exclude }
sTextBkGnd = 'Text area background';
{ @exclude }
sVertLines = 'Vertical lines';
type
{ Declares possible values for the @link(TKCustomHexEditor.AddressMode) property }
TKHexEditorAddressMode = (
{ Address will be shown in decimal format }
eamDec,
{ Address will be shown in hexadecimal format }
eamHex
);
{ Declares possible values e.g. for the @link(TKCustomHexEditor.EditArea) property }
TKHexEditorArea = (
{ No area is selected, e.g. when clicked outside of visible text }
eaNone,
{ Address area selected/used }
eaAddress,
{ Digits area selected/used }
eaDigits,
{ Text area selected/used }
eaText
);
{ @abstract(Contains dimensions of all areas in characters)
<UL>
<LH>Members:</LH>
<LI><I>Address</I> - address area width</LI>
<LI><I>AddressOut</I> - address area leadout</LI>
<LI><I>Digits</I> - digits area width</LI>
<LI><I>DigitsIn</I> - digits area leadin</LI>
<LI><I>DigitsOut</I> - digits area leadout</LI>
<LI><I>Text</I> - text area width</LI>
<LI><I>TextIn</I> - text area leadin</LI>
<LI><I>TotalHorz</I> - total width of all defined areas</LI>
<LI><I>TotalVert</I> - total number of lines</LI>
</UL>
}
TKHexEditorAreaDimensions = record
Address,
AddressOut,
Digits,
DigitsIn,
DigitsOut,
Text,
TextIn,
TotalHorz,
TotalVert: Integer;
end;
{ Declares possible indexes e.g. for the @link(TKHexEditorColors.Color) property. }
TKHexEditorColorIndex = Integer;
{ @abstract(Declares @link(TKHexEditorColors) color item description)
<UL>
<LH>Members:</LH>
<LI><I>Def</I> - default color value</LI>
<LI><I>Name</I> - color name (can be localized)</LI>
</UL>
}
TKHexEditorColorSpec = record
Def: TColor;
Name: string;
end;
{ Declares possible values for the @link(TKCustomHexEditor.DisabledDrawStyle) property }
TKHexEditorDisabledDrawStyle = (
{ The lines will be painted with brighter colors when editor is disabled }
eddBright,
{ The lines will be painted with gray text and white background when editor is disabled }
eddGrayed,
{ The lines will be painted normally when editor is disabled }
eddNormal
);
{ Declares drawing styles - possible values for the @link(TKCustomHexEditor.DrawStyles) property }
TKHexEditorDrawStyle = (
{ Show adress area }
edAddress,
{ Show digits area }
edDigits,
{ Show text area }
edText,
{ Show horizontal leading lines }
edHorzLines,
{ Show caret position when editor is inactive (has no input focus) }
edInactiveCaret,
{ Show vertical area separating lines }
edSeparators,
{ Show vertical leading lines (digits area only) }
edVertLines,
{ @link(TKHexEditorColors.BkGnd) is used for all areas if included }
edSingleBkGnd
);
{ Drawing styles can be arbitrary combined }
TKHexEditorDrawStyles = set of TKHexEditorDrawStyle;
{ @abstract(Declares the paint data structure for the @link(TKCustomHexEditor.PaintLines) method)
<UL>
<LH>Members:</LH>
<LI><I>Canvas</I> - destination canvas</LI>
<LI><I>PainRect</I> - bounding rectangle for painted lines (no clipping necessary,
this is performed by window/page client area)</LI>
<LI><I>TopLine</I> - first line painted (vertical scroll offset)</LI>
<LI><I>BottomLine</I> - last line painted</LI>
<LI><I>LeftChar</I> - first character painted (horizontal scroll offset)</LI>
<LI><I>CharWidth</I> - character width in pixels for supplied canvas</LI>
<LI><I>CharHeight</I> - character height in pixels for supplied canvas</LI>
<LI><I>CharSpacing</I> - inter-character spacing in pixels for supplied canvas</LI>
<LI><I>Printing</I> - determines whether normal painting or page printing should be performed</LI>
<LI><I>PaintAll</I> - when Printing is True, specifies whether all data or selection only
should be painted, this applies only to the first and/or last painted line</LI>
<LI><I>PaintColors</I> - when Printing is True, specifies whether to paint with colors or grayscale</LI>
<LI><I>PaintSelection</I> - when Printing is True, specifies whether to indicate the selection</LI>
</UL>
}
TKHexEditorPaintData = record
Canvas: TCanvas;
PaintRect: TRect;
TopLine,
BottomLine,
LeftChar,
CharWidth,
CharHeight,
CharSpacing: Integer;
Printing,
PaintAll,
PaintColors,
PaintSelection,
CaretShown: Boolean;
end;
{ @abstract(Declares the selection structure)
<UL>
<LH>Members:</LH>
<LI><I>Index</I> - byte index</LI>
<LI><I>Digit</I> - digit index</LI>
</UL>
}
TKHexEditorSelection = record
Index: Integer;
Digit: Integer;
end;
{ @abstract(Declares the structure for the @link(TKCustomHexEditor.SelText) property)
<UL>
<LH>Members:</LH>
<LI><I>AsBinaryRaw</I> - selected data as binary characters not mapped</LI>
<LI><I>AsBinaryMapped</I> - selected data as binary characters mapped</LI>
<LI><I>AsDigits</I> - selected data as hexadecimal digits</LI>
<LI><I>AsDigitsByteAligned</I> - selected data as hexadecimal digits
without regarding cross-byte selections</LI>
</UL>
}
TKHexEditorSelText = record
AsBinaryRaw,
AsBinaryMapped,
AsDigits,
AsDigitsByteAligned: AnsiString;
end;
{ Declares hex editor states - possible values for the @link(TKCustomHexEditor.States) property
(protected) }
TKHexEditorState = (
{ Caret is visible }
elCaretVisible,
{ Caret is being updated }
elCaretUpdate,
{ Ignore following WM_CHAR message }
elIgnoreNextChar,
{ Buffer modified }
elModified,
{ Mouse captured }
elMouseCapture,
{ Overwrite mode active }
elOverwrite,
{ Read only editor }
elReadOnly
);
{ Hex editor states can be arbitrary combined }
TKHexEditorStates = set of TKHexEditorState;
{ @abstract(Declares the color description structure returned by @link(TKHexEditorColors.ColorData) property)
<UL>
<LH>Members:</LH>
<LI><I>Index</I> - color index</LI>
<LI><I>Color</I> - current color value</LI>
<LI><I>Default</I> - default color value</LI>
<LI><I>Name</I> - color name</LI>
</UL>
}
TKHexEditorColorData = record
Index: TKHexEditorColorIndex;
Color: TColor;
Default: TColor;
Name: string;
end;
{ Declares possible values for the @link(TKHexEditorColors.ColorScheme) property }
TKHexEditorColorScheme = (
{ GetColor returns normal color currently defined for each item }
ecsNormal,
{ GetColor returns gray for text and line colors and white for background colors }
ecsGrayed,
{ GetColor returns brighter version of normal color }
ecsBright,
{ GetColor returns grayscaled color versions }
ecsGrayScale
);
const
{ Minimum for the @link(TKCustomHexEditor.AddressSize) property }
cAddressSizeMin = 2;
{ Maximum for the @link(TKCustomHexEditor.AddressSize) property }
cAddressSizeMax = 10;
{ Default value for the @link(TKCustomHexEditor.AddressSize) property }
cAddressSizeDef = 8;
{ Minimum for the @link(TKCustomHexEditor.AreaSpacing) property }
cAreaSpacingMin = 1;
{ Maximum for the @link(TKCustomHexEditor.AreaSpacing) property }
cAreaSpacingMax = 20;
{ Default value for the @link(TKCustomHexEditor.AreaSpacing) property }
cAreaSpacingDef = 1;
{ Minimum for the @link(TKCustomHexEditor.CharSpacing) property }
cCharSpacingMin = 0;
{ Maximum for the @link(TKCustomHexEditor.CharSpacing) property }
cCharSpacingMax = 100;
{ Default value for the @link(TKCustomHexEditor.CharSpacing) property }
cCharSpacingDef = 0;
{ Minimum for the @link(TKCustomHexEditor.DigitGrouping) property }
cDigitGroupingMin = 1;
{ Maximum for the @link(TKCustomHexEditor.DigitGrouping) property }
cDigitGroupingMax = 8;
{ Default value for the @link(TKCustomHexEditor.DigitGrouping) property }
cDigitGroupingDef = 2;
{ Minimum for the @link(TKCustomHexEditor.LineHeightPercent) property }
cLineHeightPercentMin = 10;
{ Maximum for the @link(TKCustomHexEditor.LineHeightPercent) property }
cLineHeightPercentMax = 1000;
{ Default value for the @link(TKCustomHexEditor.LineHeightPercent) property }
cLineHeightPercentDef = 130;
{ Minimum for the @link(TKCustomHexEditor.UndoLimit) property }
cUndoLimitMin = 100;
{ Maximum for the @link(TKCustomHexEditor.UndoLimit) property }
cUndoLimitMax = 10000;
{ Default value for the @link(TKCustomHexEditor.UndoLimit) property }
cUndoLimitDef = 1000;
{ Minimum for the @link(TKCustomHexEditor.LineSize) property }
cLineSizeMin = 1;
{ Maximum for the @link(TKCustomHexEditor.LineSize) property }
cLineSizeMax = 128;
{ Default value for the @link(TKCustomHexEditor.LineSize) property }
cLineSizeDef = 16;
{ Minimum for the @link(TKCustomHexEditor.ScrollSpeed) property }
cScrollSpeedMin = 50;
{ Maximum for the @link(TKCustomHexEditor.ScrollSpeed) property }
cScrollSpeedMax = 1000;
{ Default value for the @link(TKCustomHexEditor.ScrollSpeed) property }
cScrollSpeedDef = 100;
{ Minimum for the @link(TKHexEditor.Font).Size property }
cFontSizeMin = 8;
{ Maximum for the @link(TKHexEditor.Font).Size property }
cFontSizeMax = 100;
{ Default value for the @link(TKHexEditor.Font).Size property }
cFontSizeDef = 11;
{ Default value for the @link(TKHexEditorColors.AddressText) color property }
cAddressTextDef = clWindowText;
{ Default value for the @link(TKHexEditorColors.AddressBkGnd) color property }
cAddressBkgndDef = clWindow;
{ Default value for the @link(TKHexEditorColors.BkGnd) color property }
cBkGndDef = clWindow;
{ Default value for the @link(TKHexEditorColors.DigitTextEven) color property }
cDigitTextEvenDef = clMaroon;
{ Default value for the @link(TKHexEditorColors.DigitTextOdd) color property }
cDigitTextOddDef = clRed;
{ Default value for the @link(TKHexEditorColors.DigitBkGnd) color property }
cDigitBkGndDef = clWindow;
{ Default value for the @link(TKHexEditorColors.HorzLines) color property }
cHorzLinesDef = clWindowText;
{ Default value for the @link(TKHexEditorColors.InactiveCaretBkGnd) color property }
cInactiveCaretBkGndDef = clBlack;
{ Default value for the @link(TKHexEditorColors.InactiveCaretSelBkGnd) color property }
cInactiveCaretSelBkGndDef = clBlack;
{ Default value for the @link(TKHexEditorColors.InactiveCaretSelText) color property }
cInactiveCaretSelTextDef = clYellow;
{ Default value for the @link(TKHexEditorColors.InactiveCaretText) color property }
cInactiveCaretTextDef = clYellow;
{ Default value for the @link(TKHexEditorColors.LinesHighLight) color property }
cLinesHighLightDef = clHighLightText;
{ Default value for the @link(TKHexEditorColors.SelBkGnd) color property }
cSelBkGndDef = clGrayText;
{ Default value for the @link(TKHexEditorColors.SelBkGndFocused) color property }
cSelBkGndFocusedDef = clHighlight;
{ Default value for the @link(TKHexEditorColors.SelText) color property }
cSelTextDef = clHighlightText;
{ Default value for the @link(TKHexEditorColors.SelTextFocused) color property }
cSelTextFocusedDef = clHighlightText;
{ Default value for the @link(TKHexEditorColors.Separators) color property }
cSeparatorsDef = clWindowText;
{ Default value for the @link(TKHexEditorColors.TextText) color property }
cTextTextDef = clWindowText;
{ Default value for the @link(TKHexEditorColors.TextBkgnd) color property }
cTextBkgndDef = clWindow;
{ Default value for the @link(TKHexEditorColors.VertLines) color property }
cVertLinesDef = clWindowText;
{ Index for the @link(TKHexEditorColors.AddressText) color property }
ciAddressText = TKHexEditorColorIndex(0);
{ Index for the @link(TKHexEditorColors.AddressBkGnd) color property }
ciAddressBkGnd = TKHexEditorColorIndex(1);
{ Index for the @link(TKHexEditorColors.BkGnd) color property }
ciBkGnd = TKHexEditorColorIndex(2);
{ Index for the @link(TKHexEditorColors.DigitTextEven) color property }
ciDigitTextEven = TKHexEditorColorIndex(3);
{ Index for the @link(TKHexEditorColors.DigitTextOdd) color property }
ciDigitTextOdd = TKHexEditorColorIndex(4);
{ Index for the @link(TKHexEditorColors.DigitBkGnd) color property }
ciDigitBkGnd = TKHexEditorColorIndex(5);
{ Index for the @link(TKHexEditorColors.HorzLines) color property }
ciHorzLines = TKHexEditorColorIndex(6);
{ Index for the @link(TKHexEditorColors.InactiveCaretBkGnd) color property }
ciInactiveCaretBkGnd = TKHexEditorColorIndex(7);
{ Index for the @link(TKHexEditorColors.InactiveCaretSelBkGnd) color property }
ciInactiveCaretSelBkGnd = TKHexEditorColorIndex(8);
{ Index for the @link(TKHexEditorColors.InactiveCaretSelText) color property }
ciInactiveCaretSelText = TKHexEditorColorIndex(9);
{ Index for the @link(TKHexEditorColors.InactiveCaretText) color property }
ciInactiveCaretText = TKHexEditorColorIndex(10);
{ Index for the @link(TKHexEditorColors.LinesHighLight) color property }
ciLinesHighLight = TKHexEditorColorIndex(11);
{ Index for the @link(TKHexEditorColors.SelBkGnd) color property }
ciSelBkGnd = TKHexEditorColorIndex(12);
{ Index for the @link(TKHexEditorColors.SelBkGndFocused) color property }
ciSelBkGndFocused = TKHexEditorColorIndex(13);
{ Index for the @link(TKHexEditorColors.SelText) color property }
ciSelText = TKHexEditorColorIndex(14);
{ Index for the @link(TKHexEditorColors.SelTextFocused) color property }
ciSelTextFocused = TKHexEditorColorIndex(15);
{ Index for the @link(TKHexEditorColors.Separators) color property }
ciSeparators = TKHexEditorColorIndex(16);
{ Index for the @link(TKHexEditorColors.TextText) color property }
ciTextText = TKHexEditorColorIndex(17);
{ Index for the @link(TKHexEditorColors.TextBkgnd) color property }
ciTextBkGnd = TKHexEditorColorIndex(18);
{ Index for the @link(TKHexEditorColors.VertLines) color property }
ciVertLines = TKHexEditorColorIndex(19);
{ Maximum color array index }
ciHexEditorColorsMax = ciVertLines;
{ Default value for the @link(TKCustomHexEditor.AddressMode) property }
cAddressModeDef = eamHex;
{ Default value for the @link(TKCustomHexEditor.Addressoffset) property }
cAddressOffsetDef = 0;
{ Default value for the @link(TKCustomHexEditor.DisabledDrawStyle) property }
cDisabledDrawStyleDef = eddBright;
{ Default value for the @link(TKCustomHexEditor.DrawStyles) property }
cDrawStylesDef = [edAddress, edDigits, edText, edInactiveCaret, edSeparators];
{ Default value for the @link(TKCustomHexEditor.AddressPrefix) property }
cAddressPrefixDef = '0x';
{ Default value for the @link(TKHexEditor.Font).Name property }
cFontNameDef = {$IFDEF MSWINDOWS}'Courier New'{$ELSE}'Courier'{$ENDIF};
{ Default value for the @link(TKHexEditor.Font).Style property }
cFontStyleDef = [fsBold];
{ Declares the Index member of the @link(TKHexEditorSelection) record invalid}
cInvalidIndex = -1;
{ Default value for the @link(TKCustomHexEditor.AddressCursor) property }
cAddressCursorDef = crHandPoint;
{ Default value for the @link(TKHexEditor.Height) property }
cHeight = 300;
{ Default value for the @link(TKHexEditor.Width) property }
cWidth = 400;
type
TKCustomHexEditor = class;
{ @abstract(Container for all colors used by @link(TKCustomHexEditor) class)
This container allows to group many colors into one item in object inspector.
Colors are accessible via published properties or several public Color*
properties.
}
TKHexEditorColors = class(TPersistent)
private
FOwner: TKCustomHexEditor;
FBrightColors: TKColorArray;
FColors: TKColorArray;
FColorScheme: TKHexEditorColorScheme;
FSingleBkGnd: Boolean;
function GetColor(Index: TKHexEditorColorIndex): TColor;
function GetColorData(Index: TKHexEditorColorIndex): TKHexEditorColorData;
function GetColorEx(Index: TKHexEditorColorIndex): TColor;
function GetColorName(Index: TKHexEditorColorIndex): string;
procedure SetColor(Index: TKHexEditorColorIndex; Value: TColor);
procedure SetColorEx(Index: TKHexEditorColorIndex; Value: TColor);
procedure SetColors(const Value: TKColorArray);
public
{ Performs necessary initializations }
constructor Create(AOwner: TKCustomHexEditor);
{ Takes property values from another TKHexEditorColors class }
procedure Assign(Source: TPersistent); override;
{ Clears cached brighter colors }
procedure ClearBrightColors;
{ Initializes the color array. }
procedure Initialize; virtual;
{ Specifies color scheme for reading of published properties - see GetColor in source code}
property ColorScheme: TKHexEditorColorScheme read FColorScheme write FColorScheme;
{ Returns always normal color - regardless of the ColorScheme setting }
property Color[Index: TKHexEditorColorIndex]: TColor read GetColorEx write SetColorEx;
{ Returns always a complete color description }
property ColorData[Index: TKHexEditorColorIndex]: TKHexEditorColorData read GetColorData;
{ Returns (localizable) color name }
property ColorName[Index: TKHexEditorColorIndex]: string read GetColorName;
{ Returns array of normal colors }
property Colors: TKColorArray read FColors write SetColors;
{ @link(TKHexEditorColors.BkGnd) is used for all areas if True - @link(edSingleBkGnd) forward }
property SingleBkGnd: Boolean read FSingleBkGnd write FSingleBkGnd;
published
{ Address area text color }
property AddressText: TColor index ciAddressText read GetColor write SetColor default cAddressTextDef;
{ Address area background color }
property AddressBkGnd: TColor index ciAddressBkgnd read GetColor write SetColor default cAddressBkGndDef;
{ Hex editor client area background }
property BkGnd: TColor index ciBkGnd read GetColor write SetColor default cBkGndDef;
{ Digits area text color - even digit group }
property DigitTextEven: TColor index ciDigitTextEven read GetColor write SetColor default cDigitTextEvenDef;
{ Digits area text color - odd digit group }
property DigitTextOdd: TColor index ciDigitTextOdd read GetColor write SetColor default cDigitTextOddDef;
{ Digits area background color }
property DigitBkGnd: TColor index ciDigitBkGnd read GetColor write SetColor default cDigitBkGndDef;
{ Color of the horizontal leading lines }
property HorzLines: TColor index ciHorzLines read GetColor write SetColor default cHorzLinesDef;
{ Inactive (hex editor without focus) caret background color - caret mark is not part of a selection }
property InactiveCaretBkGnd: TColor index ciInactiveCaretBkGnd read GetColor write SetColor default cInactiveCaretBkGndDef;
{ Inactive (hex editor without focus) caret background color - caret mark is part of a selection }
property InactiveCaretSelBkGnd: TColor index ciInactiveCaretSelBkGnd read GetColor write SetColor default cInactiveCaretSelBkGndDef;
{ Inactive (hex editor without focus) caret text color - caret mark is part of a selection }
property InactiveCaretSelText: TColor index ciInactiveCaretSelText read GetColor write SetColor default cInactiveCaretSelTextDef;
{ Inactive (hex editor without focus) caret text color - caret mark is not part of a selection }
property InactiveCaretText: TColor index ciInactiveCaretText read GetColor write SetColor default cInactiveCaretTextDef;
{ Color of horizontal leading lines involved into a selection }
property LinesHighLight: TColor index ciLinesHighLight read GetColor write SetColor default cLinesHighLightDef;
{ Selection background - inactive edit area }
property SelBkGnd: TColor index ciSelBkGnd read GetColor write SetColor default cSelBkGndDef;
{ Selection background - active edit area }
property SelBkGndFocused: TColor index ciSelBkGndFocused read GetColor write SetColor default cSelBkGndFocusedDef;
{ Selection text - inactive edit area }
property SelText: TColor index ciSelText read GetColor write SetColor default cSelTextDef;
{ Selection text - active edit area }
property SelTextFocused: TColor index ciSelTextFocused read GetColor write SetColor default cSelTextFocusedDef;
{ Color of the vertical area separating lines }
property Separators: TColor index ciSeparators read GetColor write SetColor default cSeparatorsDef;
{ Text area text color }
property TextText: TColor index ciTextText read GetColor write SetColor default cTextTextDef;
{ Text area background color }
property TextBkgnd: TColor index ciTextBkgnd read GetColor write SetColor default cTextBkGndDef;
{ Color of the vertical leading lines }
property VertLines: TColor index ciVertLines read GetColor write SetColor default cVertLinesDef;
end;
{ Declares possible values for the ItemReason member of the @link(TKHexEditorChangeItem) structure }
TKHexEditorChangeReason = (
{ Save caret position only }
crCaretPos,
{ Save inserted character to be able to delete it }
crDeleteChar,
{ Save inserted hexadecimal digits to be able to delete them }
crDeleteDigits,
{ Save inserted binary string to be able to delete it }
crDeleteString,
{ Save deleted character to be able to insert it }
crInsertChar,
{ Save deleted hexadecimal digits to be able to insert them }
crInsertDigits,
{ Save deleted binary string to be able to insert it }
crInsertString
);
{ @abstract(Declares @link(TKHexEditorChangeList.OnChange) event handler)
<UL>
<LH>Parameters:</LH>
<LI><I>Sender</I> - identifies the event caller</LI>
<LI><I>ItemReason</I> - specifies the undo/redo reason</LI>
</UL>
}
TKHexEditorUndoChangeEvent = procedure(Sender: TObject;
ItemReason: TKHexEditorChangeReason) of object;
{ @abstract(Declares the undo/redo item description structure used by the @link(TKHexEditorChangeList) class)
<UL>
<LH>Members:</LH>
<LI><I>Data</I> - characters (binary or digit string) needed to execute this item</LI>
<LI><I>EditArea</I> - active edit area at the time this item was recorded</LI>
<LI><I>Group</I> - identifies the undo/redo group. Some editor modifications
produce a sequence of 2 or more undo items. This sequence is called undo/redo
group and is always interpreted as a single undo/redo item. Moreover,
if there is eoGroupUndo in @link(TKCustomHexEditor.Options),
a single ecUndo or ecRedo command manipulates all following undo groups
of the same kind (reason) as if they were a single undo/redo item. </LI>
<LI><I>GroupReason</I> - reason (kind) of this undo group</LI>
<LI><I>ItemReason</I> - reason (kind) of this item</LI>
<LI><I>SelEnd</I> - end of the selection at the time this item was recorded</LI>
<LI><I>SelStart</I> - start of the selection at the time this item was recorded</LI>
</UL>
}
TKHexEditorChangeItem = record
Data: AnsiString;
EditArea: TKHexEditorArea;
Group: Cardinal;
GroupReason: TKHexEditorChangeReason;
Inserted: Boolean;
ItemReason: TKHexEditorChangeReason;
SelEnd: TKHexEditorSelection;
SelStart: TKHexEditorSelection;
end;
{ Pointer to @link(TKHexEditorChangeItem) }
PKHexEditorChangeItem = ^TKHexEditorChangeItem;
{ @abstract(Change (undo/redo item) list manager) }
TKHexEditorChangeList = class(TList)
private
FEditor: TKCustomHexEditor;
FGroup: Cardinal;
FGroupUseLock: Integer;
FGroupReason: TKHexEditorChangeReason;
FIndex: Integer;
FModifiedIndex: Integer;
FLimit: Integer;
FRedoList: TKHexEditorChangeList;
FOnChange: TKHexEditorUndoChangeEvent;
function GetModified: Boolean;
procedure SetLimit(Value: Integer);
procedure SetModified(Value: Boolean);
protected
{ Redefined to properly destroy the items }
procedure Notify(Ptr: Pointer; Action: TListNotification); override;
public
{ Performs necessary initializations
<UL>
<LH>Parameters:</LH>
<LI><I>AEditor</I> - identifies the undo/redo list owner</LI>
<LI><I>RedoList</I> - when this instance is used as undo list, specify
a redo list to allow clear it at each valid AddChange call</LI>
</UL>}
constructor Create(AEditor: TKCustomHexEditor; RedoList: TKHexEditorChangeList);
{ Inserts a undo/redo item
<UL>
<LH>Parameters:</LH>
<LI><I>ItemReason</I> - specifies the undo/redo item reason. The change list doesn't
allow to insert succesive crCaretPos items unless Inserted is True</LI>
<LI><I>Data</I> - specifies the item data. Some items (crCaretPos)
don't need to supply any data</LI>
<LI><I>Inserted</I> - for the urInsert* items, specifies whether the item
was recorded with @link(TKCustomHexEditor.InsertMode) on (True) or
off (False). See ItemReason for crCaretPos behavior.</LI>
</UL>}
procedure AddChange(ItemReason: TKHexEditorChangeReason; const Data: AnsiString = '';
Inserted: Boolean = True); virtual;
{ Tells the undo list a new undo/redo group is about to be created. Each
BeginGroup call must have a corresponding EndGroup call (use try-finally).
BeginGroup calls may be nested, however, only the first call will create an
undo/redo group. Use the GroupReason parameter to specify the reason of this group. }
procedure BeginGroup(GroupReason: TKHexEditorChangeReason); virtual;
{ Informs whether there are any undo/redo items available - i.e. CanUndo/CanRedo}
function CanPeek: Boolean;
{ Clears the entire list - overriden to execute some adjustments }
procedure Clear; override;
{ Completes the undo/redo group. See @link(TKHexEditorChangeList.BeginGroup) for details }
procedure EndGroup; virtual;
{ Returns the topmost item to handle or inspect it}
function PeekItem: PKHexEditorChangeItem;
{ If there is no reason to handle an item returned by PeekItem, it has to be
poked back with this function to become active for next undo/redo command }
procedure PokeItem;
{ For redo list only - each undo command creates a redo command with the same
group information - see source }
procedure SetGroupData(Group: Integer; GroupReason: TKHexEditorChangeReason);
{ Specifies maximum number of items - not groups }
property Limit: Integer read FLimit write SetLimit;
{ For undo list only - returns True if undo list contains some items with regard
to the eoUndoAfterSave option }
property Modified: Boolean read GetModified write SetModified;
{ Allows to call TKCustomHexEditor.@link(TKCustomHexEditor.OnChange) event}
property OnChange: TKHexEditorUndoChangeEvent read FOnChange write FOnChange;
end;
{ @abstract(Hexadecimal editor base component) }
TKCustomHexEditor = class(TKCustomControl)
private
FAddressCursor: TCursor;
FAddressMode: TKHexEditorAddressMode;
FAddressOffset: Integer;
FAddressPrefix: string;
FAddressSize: Integer;
FAreaSpacing: Integer;
FBuffer: PBytes;
FCharHeight: Integer;
FCharMapping: TKEditCharMapping;
FCharSpacing: Integer;
FCharWidth: Integer;
FClipboardFormat: Word;
FColors: TKHexEditorColors;
FDigitGrouping: Integer;
FDisabledDrawStyle: TKHexEditorDisabledDrawStyle;
FDrawStyles: TKHexEditorDrawStyles;
FEditArea: TKHexEditorArea;
FKeyMapping: TKEditKeyMapping;
FLeftChar: Integer;
FLineHeightPercent: Integer;
FLineSize: Integer;
FMouseWheelAccumulator: Integer;
FOptions: TKEditOptions;
FRedoList: TKHexEditorChangeList;
FScrollBars: TScrollStyle;
FScrollDeltaX: Integer;
FScrollDeltaY: Integer;
FScrollSpeed: Cardinal;
FScrollTimer: TTimer;
FSelEnd: TKHexEditorSelection;
FSelStart: TKHexEditorSelection;
FSize: Integer;
FStates: TKHexEditorStates;
FTopLine: Integer;
FTotalCharSpacing: Integer;
FUndoList: TKHexEditorChangeList;
FOnChange: TNotifyEvent;
FOnDropFiles: TKEditDropFilesEvent;
FOnReplaceText: TKEditReplaceTextEvent;
function GetCommandKey(Index: TKEditCommand): TKEditKey;
function GetCaretVisible: Boolean;
function GetData: TDataSize;
function GetEmpty: Boolean;
function GetFirstVisibleIndex: Integer;
function GetInsertMode: Boolean;
function GetLastVisibleIndex: Integer;
function GetLineCount: Integer;
function GetLines(Index: Integer): TDataSize;
function GetModified: Boolean;
function GetReadOnly: Boolean;
function GetSelLength: TKHexEditorSelection;
function GetSelText: TKHexEditorSelText;
function GetUndoLimit: Integer;
function IsAddressPrefixStored: Boolean;
function IsDrawStylesStored: Boolean;
function IsOptionsStored: Boolean;
procedure ScrollTimerHandler(Sender: TObject);
procedure SetAddressCursor(Value: TCursor);
procedure SetAddressMode(Value: TKHexEditorAddressMode);
procedure SetAddressOffset(Value: Integer);
procedure SetAddressPrefix(const Value: string);
procedure SetAddressSize(Value: Integer);
procedure SetAreaSpacing(Value: Integer);
procedure SetCharSpacing(Value: Integer);
procedure SetColors(Value: TKHexEditorColors);
procedure SetCommandKey(Index: TKEditCommand; Value: TKEditKey);
procedure SetData(Value: TDataSize);
procedure SetDigitGrouping(Value: Integer);
procedure SetDisabledDrawStyle(Value: TKHexEditorDisabledDrawStyle);
procedure SetDrawStyles(const Value: TKHexEditorDrawStyles);
procedure SetEditArea(Value: TKHexEditorArea);
procedure SetLeftChar(Value: Integer);
procedure SetLineHeightPercent(Value: Integer);
procedure SetLines(Index: Integer; const Value: TDataSize);
procedure SetLineSize(Value: Integer);
procedure SetModified(Value: Boolean);
procedure SetOptions(const Value: TKEditOptions);
procedure SetReadOnly(Value: Boolean);
procedure SetScrollBars(Value: TScrollStyle);
procedure SetScrollSpeed(Value: Cardinal);
procedure SetSelEnd(Value: TKHexEditorSelection);
procedure SetSelLength(Value: TKHexEditorSelection);
procedure SetSelStart(Value: TKHexEditorSelection);
procedure SetTopLine(Value: Integer);
procedure SetUndoLimit(Value: Integer);
procedure CMEnabledChanged(var Msg: TLMessage); message CM_ENABLEDCHANGED;
procedure CMSysColorChange(var Msg: TLMessage); message CM_SYSCOLORCHANGE;
{$IFNDEF FPC}
// no way to get filenames in Lazarus inside control (why??)
procedure WMDropFiles(var Msg: TLMessage); message LM_DROPFILES;
{$ENDIF}
procedure WMEraseBkgnd(var Msg: TLMessage); message LM_ERASEBKGND;
procedure WMGetDlgCode(var Msg: TLMNoParams); message LM_GETDLGCODE;
procedure WMHScroll(var Msg: TLMHScroll); message LM_HSCROLL;
procedure WMKillFocus(var Msg: TLMKillFocus); message LM_KILLFOCUS;
procedure WMSetFocus(var Msg: TLMSetFocus); message LM_SETFOCUS;
procedure WMVScroll(var Msg: TLMVScroll); message LM_VSCROLL;
protected
{ Inserts a single crCaretPos item into undo list. Unless Force is set to True,
this change will be inserted only if previous undo item is not crCaretPos. }
procedure AddUndoCaretPos(Force: Boolean = True);
{ Inserts a single byte change into undo list.
<UL>
<LH>Parameters:</LH>
<LI><I>ItemReason</I> - specifies the undo/redo item reason - most likely
crInsertChar or crDeleteChar.</LI>
<LI><I>Data</I> - specifies the data byte needed to restore the original
buffer state</LI>
<LI><I>Inserted</I> - for the urInsert* items, specifies the current
@link(TKCustomHexEditor.InsertMode) status.</LI>
</UL>}
procedure AddUndoByte(ItemReason: TKHexEditorChangeReason; Data: Byte;
Inserted: Boolean = True);
{ Inserts a byte array change into undo list.
<UL>
<LH>Parameters:</LH>
<LI><I>ItemReason</I> - specifies the undo/redo item reason - crInsert* or
crDelete*.</LI>
<LI><I>Data</I> - specifies the data bytes needed to restore the original
buffer state</LI>
<LI><I>Inserted</I> - for the urInsert* items, specifies the current
@link(TKCustomHexEditor.InsertMode) status.</LI>
</UL>}
procedure AddUndoBytes(ItemReason: TKHexEditorChangeReason; Data: PBytes;
Length: Integer; Inserted: Boolean = True);
{ Inserts a string change into undo list. Has the same functionality as AddUndoBytes
only Data is supplied as a string. }
procedure AddUndoString(ItemReason: TKHexEditorChangeReason; const S: AnsiString;
Inserted: Boolean = True);
{ Begins a new undo group. Use the GroupReason parameter to label it. }
procedure BeginUndoGroup(GroupReason: TKHexEditorChangeReason);
{ Performs necessary adjustments when the buffer is modified programatically
(not by user) }
procedure BufferChanged;
{ Determines whether an ecScroll* command can be executed }
function CanScroll(Command: TKEditCommand): Boolean; virtual;
{ Clears a character at position At. Doesn't perform any succesive adjustments. }
procedure ClearChar(At: Integer);
{ Clears a the digit fields both in SelStart and SelEnd. Doesn't perform any succesive adjustments.}
procedure ClearDigitSelection;
{ Clears a string of the Size length at position At. Doesn't perform any succesive adjustments. }
procedure ClearString(At, Size: Integer);
{ Overriden method - defines additional styles for the hex editor window (scrollbars etc.)}
procedure CreateParams(var Params: TCreateParams); override;
{ Overriden method - adjusts file drag&drop functionality }
procedure CreateWnd; override;
{ Overriden method - adjusts file drag&drop functionality }
procedure DestroyWnd; override;
{ Calls the @link(TKCustomHexEditor.OnChange) event }
procedure DoChange; virtual;
{ Overriden method - handles mouse wheel messages }
function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
MousePos: TPoint): Boolean; override;
{ Validates the EditArea property after it has been modified }
procedure EditAreaChanged; virtual;
{ Closes the undo group created by @link(TKCustomHexEditor.BeginUndoGroup) }
procedure EndUndoGroup;
{ Ensures that font pitch is always fpFixed and Font.Size is not too small or big }
procedure FontChange(Sender: TObject); virtual;
{ Returns the horizontal page extent for the current edit area. This function is
used by the ecPageLeft and ecPageRight commands. }
function GetPageHorz: Integer; virtual;
{ Determines if the editor has input focus. }
function HasFocus: Boolean; virtual;
{ Hides the caret. }
procedure HideEditorCaret; virtual;
{ Inserts a character at specified position. Doesn't perform any succesive adjustments.
<UL>
<LH>Parameters:</LH>
<LI><I>At</I> - position where the character should be inserted.</LI>
<LI><I>Value</I> - character (data byte)</LI>
</UL> }
procedure InsertChar(At: Integer; Value: Byte);
{ Inserts a string at specified position. Doesn't perform any succesive adjustments.
<UL>
<LH>Parameters:</LH>
<LI><I>At</I> - position where the string should be inserted.</LI>
<LI><I>Value</I> - data byte string</LI>
<LI><I>Size</I> - length of the data byte string</LI>
</UL> }
procedure InsertString(At: Integer; const Value: AnsiString; Size: Integer);
{ Returns True if the control has a selection. }
function InternalGetSelAvail: Boolean; override;
{ Moves the caret one position left. Doesn't perform any succesive adjustments.}
procedure InternalMoveLeft; virtual;
{ Moves the caret one position right. Doesn't perform any succesive adjustments.}
procedure InternalMoveRight; virtual;
{ Overriden method - processes virtual key strokes according to current @link(TKCustomHexEditor.KeyMapping) }
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
{ Overriden method - processes character key strokes - data editing }
procedure KeyPress(var Key: Char); override;
{ Updates information about printed shape. }
procedure MeasurePages(var Info: TKPrintMeasureInfo); override;
{ Processes scrollbar messages.
<UL>
<LH>Parameters:</LH>
<LI><I>ScrollBar</I> - scrollbar type from OS</LI>
<LI><I>ScrollCode</I> - scrollbar action from OS</LI>
<LI><I>Delta</I> - scrollbar position change</LI>
<LI><I>UpdateNeeded</I> - set to True if you want to invalidate
and update caret position</LI>
</UL> }
procedure ModifyScrollBar(ScrollBar, ScrollCode, Delta: Integer;
UpdateNeeded: Boolean);
{ Overriden method - updates caret position/selection }
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
{ Overriden method - updates caret position/selection and initializes scrolling
when needed. }
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
{ Overriden method - releases mouse capture acquired by MouseDown }
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
{ Overriden method - calls PaintLines for drawing the hex editor outline
into window client area }
procedure PaintToCanvas(ACanvas: TCanvas); override;
{ Paints/prints hex editor outline. This function must retain its reentrancy.
<UL>
<LH>Parameters:</LH>
<LI><I>Data</I> - paint settings</LI>
</UL> }
procedure PaintLines(const Data: TKHexEditorPaintData); virtual;
{ Paints a page to a printer/preview canvas. }
procedure PaintPage; override;
{ Grants the input focus to the control when possible and the control has had none before }
procedure SafeSetFocus;
{ Performs necessary adjustments after a selection property changed.
<UL>
<LH>Parameters:</LH>
<LI><I>StartEqualEnd</I> - forces SelStart equal to SelEnd</LI>
<LI><I>ScrollToView</I> - forces scrolling if SelEnd (caret) became invisible</LI>
</UL> }
procedure SelectionChanged(StartEqualEnd: Boolean; ScrollToView: Boolean = True);
{ Scrolls the hex editor window horizontaly by HChars characters and/or
vertically by VChars characters }
procedure ScrollBy(HChars, VChars: Integer; UpdateNeeded: Boolean);
{ Scrolls the hex editor window to ensure data under defined (mouse) coordinates are visible
<UL>
<LH>Parameters:</LH>
<LI><I>Point</I> - (mouse) coordinates</LI>
<LI><I>Timed</I> - set to True to continue scroll via a timer. The scrolling
will continue until the mouse cursor is outside of the modified client rect
(@link(TKCustomHexEditor.GetModifiedClientRect)).</LI>
<LI><I>AlwaysScroll</I> - set to True to disable new line overscrolling</LI>
</UL> }
procedure ScrollTo(Point: TPoint; Timed, AlwaysScroll: Boolean); virtual;
{ Updates mouse cursor according to the state determined from current mouse
position. Returns True if cursor has been changed. }
function SetMouseCursor(X, Y: Integer): Boolean; override;
{ Shows the caret. }
procedure ShowEditorCaret; virtual;
{ Calls the @link(TKCustomHexEditor.DoChange) method}
procedure UndoChange(Sender: TObject; ItemReason: TKHexEditorChangeReason);
{ Updates caret position, shows/hides caret according to the input focus
<UL>
<LH>Parameters:</LH>
<LI><I>Recreate</I> - set to True to recreate the caret after it has already
been created and displayed</LI>
</UL> }
procedure UpdateEditorCaret(Recreate: Boolean = False); virtual;
{ Updates font based dimensions }
procedure UpdateCharMetrics; virtual;
{ Updates mouse cursor }
procedure UpdateMouseCursor; virtual;
{ Updates the scrolling range }
procedure UpdateScrollRange; virtual;
{ Updates selection according to the supplied coordinates.
<UL>
<LH>Parameters:</LH>
<LI><I>Point</I> - specifies the coordinates </LI>
<LI><I>ClipToClient</I> - specifies whether the coordinates should be clipped
to modified client rectangle (@link(TKCustomHexEditor.GetModifiedClientRect))
first</LI>
</UL> }
procedure UpdateSelEnd(Point: TPoint; ClipToClient: Boolean); virtual;
{ Updates the control size. }
procedure UpdateSize; override;
{ Data buffer - made accessible for descendant classes }
property Buffer: PBytes read FBuffer write FBuffer;
{ Redo list manager - made accessible for descendant classes }
property RedoList: TKHexEditorChangeList read FRedoList;
{ Data buffer size - made accessible for descendant classes }
property Size: Integer read FSize write FSize;
{ States of this class - made accessible for descendant classes }
property States: TKHexEditorStates read FStates write FStates;
{ Undo list manager - made accessible for descendant classes }
property UndoList: TKHexEditorChangeList read FUndoList;
public
{ Performs necessary initializations - default values to properties, create
undo/redo list managers }
constructor Create(AOwner: TComponent); override;
{ Destroy instance, undo/redo list managers, dispose buffer... }
destructor Destroy; override;
{ Appends data at current position. Use TKHexEditor.Data.Size for At parameter
to append at the end of the buffer. }
procedure Append(At: Integer; Data: TDataSize); overload; virtual;
{ Appends data at current position. Use TKHexEditor.Data.Size for At parameter
to append at the end of the buffer. }
procedure Append(At: Integer; const Data: AnsiString); overload; virtual;
{ Takes property values from another TKCustomHexEditor class }
procedure Assign(Source: TPersistent); override;
{ Determines whether the caret is visible }
function CaretInView: Boolean;
{ Clears entire data buffer. Unlike ecClearAll this method clears everything
inclusive undo a redo lists. }
procedure Clear;
{ Clears undo (and redo) list }
procedure ClearUndo;
{ Determines whether given command can be executed at this time. Use this
function in TAction.OnUpdate events.
<UL>
<LH>Parameters:</LH>
<LI><I>Command</I> - specifies the command to inspect</LI>
</UL> }
function CommandEnabled(Command: TKEditCommand): Boolean; virtual;
{ Executes given command. This function first calls CommandEnabled to
assure given command can be executed.
<UL>
<LH>Parameters:</LH>
<LI><I>Command</I> - specifies the command to execute</LI>
<LI><I>Data</I> - specifies the data needed for the command</LI>
</UL> }
function ExecuteCommand(Command: TKEditCommand; Data: Pointer = nil): Boolean; virtual;
{ Returns dimensions of all 3 possible areas according to current area
definition }
function GetAreaDimensions: TKHexEditorAreaDimensions; virtual;
{ Returns current character mapping. }
function GetCharMapping: TKEditCharMapping;
{ Returns number of characters that vertically fit into client window }
function GetClientHeightChars: Integer; virtual;
{ Returns number of characters that horizontally fit into client window }
function GetClientWidthChars: Integer; virtual;
{ Returns the current key stroke mapping scheme. }
function GetKeyMapping: TKEditKeyMapping;
{ Returns modified client rect - a window client rect aligned to character width and
character height }
function GetModifiedClientRect: TRect; virtual;
{ Returns current maximum value for the @link(TKCustomHexEditor.LeftChar) property
<UL>
<LH>Parameters:</LH>
<LI><I>Extent</I> - specify @link(TKHexEditorAreaDimensions).TotalHorz
here, otherwise the function calculates it itself</LI>
</UL> }
function GetMaxLeftChar(Extent: Integer = 0): Integer; virtual;
{ Returns current maximum value for the @link(TKCustomHexEditor.TopLine) property
<UL>
<LH>Parameters:</LH>
<LI><I>Extent</I> - specify @link(TKHexEditorAreaDimensions).TotalVert
here, otherwise the function calculates it itself</LI>
</UL> }
function GetMaxTopLine(Extent: Integer = 0): Integer; virtual;
{ Returns "real" selection end - with always higher index value than selection
start value }
function GetRealSelEnd: TKHexEditorSelection;
{ Returns "real" selection start - with always lower index value than selection
end value }
function GetRealSelStart: TKHexEditorSelection;
{ Loads data from a file }
procedure LoadFromFile(const FileName: TFileName);
{ Loads data from a stream - stream position remains untouched }
procedure LoadFromStream(Stream: TStream);
{ Paints the editor outline to another canvas
<UL>
<LH>Parameters:</LH>
<LI><I>ACanvas</I> - canvas to paint the outline to</LI>
<LI><I>ARect</I> - given rectangle in the canvas</LI>
<LI><I>ALeftChar</I> - first left visible character</LI>
<LI><I>ATopLine</I> - first top visible line</LI>
</UL> }
procedure PaintToCanvasEx(ACanvas: TCanvas; ARect: TRect; ALeftChar, ATopLine: Integer);
{ Converts window coordinates into a selection
<UL>
<LH>Parameters:</LH>
<LI><I>P</I> - window client coordinates</LI>
<LI><I>OutOfArea</I> - uses the Area parameter to compute selection for
this area even if the supplied coordinates are outside of the area outline</LI>
<LI><I>Area</I> output parameter if OutOfArea = False, otherwise
input parameter</LI>
</UL> }
function PointToSel(P: TPoint; OutOfArea: Boolean; var Area: TKHexEditorArea): TKHexEditorSelection; virtual;
{ Saves data into a file }
procedure SaveToFile(const FileName: TFileName);
{ Saves data into a stream - stream position remains untouched }
procedure SaveToStream(Stream: TStream);
{ Determines whether a seletion (not digit selection) is available }
function SelAvail: Boolean;
{ Determines whether a given selection is valid for given area
<UL>
<LH>Parameters:</LH>
<LI><I>Value</I> - selection to examine</LI>
<LI><I>Area</I> - area for which the selection must be examined</LI>
</UL> }
function SelectionValid(Value: TKHexEditorSelection; Area: TKHexEditorArea): Boolean; virtual;
{ Converts a selection into window coordinates
<UL>
<LH>Parameters:</LH>
<LI><I>Value</I> - selection to convert</LI>
<LI><I>Area</I> - the same selection delivers another coordinates for each area</LI>
</UL> }
function SelToPoint(Value: TKHexEditorSelection; Area: TKHexEditorArea): TPoint; virtual;
{ Specifies character mapping. The main purpose of this is to avoid non-printable
characters in the text area and in AsText copies. Avoid non-printable characters
when delivering a new character mapping. }
procedure SetCharMapping(const Value: TKEditCharMapping);
{ Specifies the current key stroke mapping scheme }
procedure SetKeyMapping(const Value: TKEditKeyMapping);
{ Validates a selection for given area
<UL>
<LH>Parameters:</LH>
<LI><I>Value</I> - selection to validate</LI>
<LI><I>Area</I> - area for which the selection must be validated</LI>
</UL> }
procedure ValidateSelection(var Value: TKHexEditorSelection; Area: TKHexEditorArea); virtual;
{ Specifies the address area mouse cursor. Other areas have crIBeam - should not
be needed to modify that }
property AddressCursor: TCursor read FAddressCursor write SetAddressCursor default cAddressCursorDef;
{ Specifies the radix of addresses }
property AddressMode: TKHexEditorAddressMode read FAddressMode write SetAddressMode default cAddressModeDef;
{ Specifies the address offset }
property AddressOffset: Integer read FAddressOffset write SetAddressOffset default cAddressOffsetDef;
{ Specifies the address number prefix i.e. 0x or $ - modify together with AddressMode }
property AddressPrefix: string read FAddressPrefix write SetAddressPrefix stored IsAddressPrefixStored;
{ Specifies the number of address digits - up to 10 for decimal addresses }
property AddressSize: Integer read FAddressSize write SetAddressSize default cAddressSizeDef;
{ Defines space between neighbour areas }
property AreaSpacing: Integer read FAreaSpacing write SetAreaSpacing default cAreaSpacingDef;
{ Returns current caret position = selection end }
property CaretPos: TKHexEditorSelection read FSelEnd;
{ Returns True if caret is visible }
property CaretVisible: Boolean read GetCaretVisible;
{ Returns current character width = not necessarily equal to font character width }
property CharWidth: Integer read FCharWidth;
{ Defines additional inter-character spacing }
property CharSpacing: Integer read FCharSpacing write SetCharSpacing default cCharSpacingDef;
{ Returns current character height = not equal to font character height }
property CharHeight: Integer read FCharHeight;
{ Returns the binary data clipboard format }
property ClipboardFormat: Word read FClipboardFormat;
{ Makes it possible to take all color properties from another TKCustomHexEditor class }
property Colors: TKHexEditorColors read FColors write SetColors;
{ Specifies a new key stroke combination for given command }
property CommandKey[Index: TKEditCommand]: TKEditKey read GetCommandKey write SetCommandKey;
{ This property provides direct access to the data buffer }
property Data: TDataSize read GetData write SetData;
{ Specifies the byte grouping in the digits area }
property DigitGrouping: Integer read FDigitGrouping write SetDigitGrouping default cDigitGroupingDef;
{ Specifies the style how the outline is drawn when editor is disabled }
property DisabledDrawStyle: TKHexEditorDisabledDrawStyle read FDisabledDrawStyle write SetDisabledDrawStyle default cDisabledDrawStyleDef;
{ Defines areas to paint, whether to paint horizontal and vertical trailing lines,
area separator lines and caret mark when the editor has no input focus }
property DrawStyles: TKHexEditorDrawStyles read FDrawStyles write SetDrawStyles stored IsDrawStylesStored;
{ Specifies the current area for editing }
property EditArea: TKHexEditorArea read FEditArea write SetEditArea default eaDigits;
{ Returns True if data buffer is empty }
property Empty: Boolean read GetEmpty;
{ Returns the first visible index }
property FirstVisibleIndex: Integer read GetFirstVisibleIndex;
{ Returns True if insert mode is on }
property InsertMode: Boolean read GetInsertMode;
{ Returns the last visible index }
property LastVisibleIndex: Integer read GetLastVisibleIndex;
{ Specifies the horizontal scroll position }
property LeftChar: Integer read FLeftChar write SetLeftChar;
{ Determines the number of lines }
property LineCount: Integer read GetLineCount;
{ Specifies the line height. 100% is the current font height }
property LineHeightPercent: Integer read FLineHeightPercent write SetLineHeightPercent default cLineHeightPercentDef;
{ Allows to modify/add data lines. If greater than LineSize, the Size member
of the supplied TDataSize structure will be always trimmed to LineSize.
If Index points to last incomplete line or even higher, last line will be
extended/completed, i.e new data will be added to the buffer }
property Lines[Index: Integer]: TDataSize read GetLines write SetLines;
{ Specifies the size (length) of a single line }
property LineSize: Integer read FLineSize write SetLineSize default cLineSizeDef;
{ Returns True if the buffer was modified - eoUndoAfterSave taken into
account }
property Modified: Boolean read GetModified write SetModified;
{ Specifies the editor options that do not affect painting }
property Options: TKEditOptions read FOptions write SetOptions stored IsOptionsStored;
{ Specifies whether the editor has to be read only editor }
property ReadOnly: Boolean read GetReadOnly write SetReadOnly default False;
{ Defines visible scrollbars - horizontal, vertical or both }
property ScrollBars: TScrollStyle read FScrollBars write SetScrollBars default ssBoth;
{ Specifies how fast the scrolling by timer should be }
property ScrollSpeed: Cardinal read FScrollSpeed write SetScrollSpeed default cScrollSpeedDef;
{ Specifies the current selection end }
property SelEnd: TKHexEditorSelection read FSelEnd write SetSelEnd;
{ Specifies the current selection length. SelStart remains unchanged, SelEnd will be
updated accordingly. To mark a selection, either set both SelStart and SelEnd properties
or both SelStart and SelLength properties }
property SelLength: TKHexEditorSelection read GetSelLength write SetSelLength;
{ Specifies the current selection start }
property SelStart: TKHexEditorSelection read FSelStart write SetSelStart;
{ Returns selected text in many different formats }
property SelText: TKHexEditorSelText read GetSelText;
{ Specifies the vertical scroll position }
property TopLine: Integer read FTopLine write SetTopLine;
{ Specifies the maximum number of undo items. Please note this value
affects the undo item limit, not undo group limit. }
property UndoLimit: Integer read GetUndoLimit write SetUndoLimit default cUndoLimitDef;
{ When assigned, this event will be invoked at each buffer change, made either
by the user or programmatically by public functions }
property OnChange: TNotifyEvent read FOnChange write FOnChange;
{ When assigned, this event will be invoked when the user drops any files onto
the window }
property OnDropFiles: TKEditDropFilesEvent read FOnDropFiles write FOnDropFiles;
{ When assigned, this event will be invoked at each prompt-forced search match }
property OnReplaceText: TKEditReplaceTextEvent read FOnReplaceText write FOnReplaceText;
end;
{ @abstract(Hexadecimal editor design-time component) }
TKHexEditor = class(TKCustomHexEditor)
published
{ See TKCustomHexEditor.@link(TKCustomHexEditor.AddressCursor) for details }
property AddressCursor;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.AddressMode) for details }
property AddressMode;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.AddressOffset) for details }
property AddressOffset;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.AddressPrefix) for details }
property AddressPrefix;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.AddressSize) for details }
property AddressSize;
{ Inherited property - see Delphi help }
property Align;
{ Inherited property - see Delphi help }
property Anchors;
{ See TKCustomControl.@link(TKCustomControl.BorderStyle) for details }
property BorderStyle;
{ Inherited property - see Delphi help }
property BorderWidth;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.CharSpacing) for details }
property CharSpacing;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.Colors) for details }
property Colors;
{ Inherited property - see Delphi help }
property Constraints;
{$IFNDEF FPC}
{ Inherited property - see Delphi help. }
property Ctl3D;
{$ENDIF}
{ See TKCustomHexEditor.@link(TKCustomHexEditor.DigitGrouping) for details }
property DigitGrouping;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.DisabledDrawStyle) for details }
property DisabledDrawStyle;
{ Inherited property - see Delphi help }
property DragCursor;
{ Inherited property - see Delphi help }
property DragKind;
{ Inherited property - see Delphi help }
property DragMode;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.DrawStyles) for details }
property DrawStyles;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.EditArea) for details }
property EditArea;
{ Inherited property - see Delphi help }
property Enabled;
{ Inherited property - see Delphi help. Font pitch must always remain fpFixed
- specify fixed fonts only. Font.Size will also be trimmed if too small or big }
property Font;
{ Inherited property - see Delphi help }
property Height default cHeight;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.LineHeightPercent) for details }
property LineHeightPercent;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.LineSize) for details }
property LineSize;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.Options) for details }
property Options;
{ Inherited property - see Delphi help }
property ParentShowHint;
{ Inherited property - see Delphi help }
property PopupMenu;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.ReadOnly) for details }
property ReadOnly;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.ScrollBars) for details }
property ScrollBars;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.ScrollSpeed) for details }
property ScrollSpeed;
{ Inherited property - see Delphi help }
property ShowHint;
{ Inherited property - see Delphi help }
property TabOrder;
{ Inherited property - see Delphi help }
property TabStop default True;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.UndoLimit) for details }
property UndoLimit;
{ Inherited property - see Delphi help }
property Visible;
{ Inherited property - see Delphi help }
property Width default cWidth;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.OnChange) for details }
property OnChange;
{ Inherited property - see Delphi help }
property OnClick;
{ Inherited property - see Delphi help }
property OnContextPopup;
{ Inherited property - see Delphi help }
property OnDblClick;
{ Inherited property - see Delphi help }
property OnDockDrop;
{ Inherited property - see Delphi help }
property OnDockOver;
{ Inherited property - see Delphi help }
property OnDragDrop;
{ Inherited property - see Delphi help }
property OnDragOver;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.OnDropFiles) for details }
property OnDropFiles;
{ Inherited property - see Delphi help }
property OnEndDock;
{ Inherited property - see Delphi help }
property OnEndDrag;
{ Inherited property - see Delphi help }
property OnEnter;
{ Inherited property - see Delphi help }
property OnExit;
{ Inherited property - see Delphi help }
property OnGetSiteInfo;
{ Inherited property - see Delphi help }
property OnKeyDown;
{ Inherited property - see Delphi help }
property OnKeyPress;
{ Inherited property - see Delphi help }
property OnKeyUp;
{ Inherited property - see Delphi help }
property OnMouseDown;
{ Inherited property - see Delphi help }
property OnMouseMove;
{ Inherited property - see Delphi help }
property OnMouseUp;
{ Inherited property - see Delphi help }
property OnMouseWheel;
{ Inherited property - see Delphi help }
property OnMouseWheelDown;
{ Inherited property - see Delphi help }
property OnMouseWheelUp;
{ See TKCustomControl.@link(TKCustomControl.OnPrintNotify) for details }
property OnPrintNotify;
{ See TKCustomControl.@link(TKCustomControl.OnPrintPaint) for details }
property OnPrintPaint;
{ See TKCustomHexEditor.@link(TKCustomHexEditor.OnReplaceText) for details }
property OnReplaceText;
{ Inherited property - see Delphi help }
property OnResize;
{ Inherited property - see Delphi help }
property OnStartDock;
{ Inherited property - see Delphi help }
property OnStartDrag;
{ Inherited property - see Delphi help }
property OnUnDock;
end;
{ Creates a selection structure from given Index and Digit parameters }
function MakeSelection(Index, Digit: Integer): TKHexEditorSelection;
{ Converts a hexadecimal digit character ('0'..'F') to binary value }
function DigitToBin(Value: AnsiChar): Integer;
{ Examines/converts hexadecimal digit string to binary value string. Returns
True if the digit string is valid.
<UL>
<LH>Parameters:</LH>
<LI><I>S</I> - hexadecimal digit string (e.g. 'AF01 DC05 3'). White spaces will
be ignored. When Convert is True, the converted binary value string will be returned
via this parameter (in this exammple '#A#F#0#1#D#C#0#5#3').</LI>
<LI><I>Convert</I> - the digit string will be converted if True, otherwise it will
be examined only.</LI>
</UL> }
function DigitsToBinStr(var S: AnsiString; Convert: Boolean = True): Boolean;
{ Converts a binary value string into binary data. If the binary value string
is not divisible by 2, it will be right padded with zero. Example:
'#A#F#0#1#D#C#0#5#3' is converted into '#AF#01#DC#05#30'. }
function BinStrToBinary(const S: AnsiString): AnsiString;
{ Converts binary data into hexadecimal digit string.
<UL>
<LH>Parameters:</LH>
<LI><I>Buffer</I> - binary data - intended for @link(TKCustomHexEditor.Buffer)</LI>
<LI><I>SelStart, SelEnd</I> - specifies which part of the buffer is about to be
converted. SelStart.Index must be lower or equal to SelEnd.Index - intended for
@link(TKCustomHexEditor.GetRealSelStart) and @link(TKCustomHexEditor.GetRealSelEnd).</LI>
</UL> }
function BinaryToDigits(Buffer: PBytes; SelStart, SelEnd: TKHexEditorSelection): AnsiString;
{ Converts binary data into text using given character mapping.
<UL>
<LH>Parameters:</LH>
<LI><I>Buffer</I> - binary data - intended for @link(TKCustomHexEditor.Buffer)</LI>
<LI><I>SelStart, SelEnd</I> - specifies which part of the buffer is about to be
converted. SelStart must be lower or equal to SelEnd. These parameters are integers
since no digit selections are necessary.</LI>
<LI><I>CharMapping</I> - required character mapping scheme</LI>
</UL> }
function BinaryToText(Buffer: PBytes; SelStart, SelEnd: Integer;
CharMapping: PKEditCharMapping): AnsiString;
{ Replaces a hexadecimal digit in the given binary value. Returns the original
value with a replaced digit.
<UL>
<LH>Parameters:</LH>
<LI><I>Value</I> - original binary value</LI>
<LI><I>Digit</I> - digit value (0..15)</LI>
<LI><I>Pos</I> - digit position (order)</LI>
</UL>
Example: Value = $A18D, Digit = $C, Pos = 3: Result = $AC8D }
function ReplaceDigit(Value, Digit, Pos: Integer): Integer;
{ Returns the instance-independent color specification for
the given color index }
function GetColorSpec(Index: TKHexEditorColorIndex): TKHexEditorColorSpec;
implementation
uses
{$IFDEF USE_THEMES}
Themes,
{$ENDIF}
Math,
{$IFDEF USE_WINAPI}
ShellApi,
{$ENDIF}
ClipBrd, Printers,
Types, KGraphics;
const
cFmtText = '%.2x';
cBase = 16;
cDigitCount = 2;
function MakeSelection(Index, Digit: Integer): TKHexEditorSelection;
begin
Result.Index := Index;
Result.Digit := Digit;
end;
function DigitToBin(Value: AnsiChar): Integer;
begin
if ((Value >= 'a') and (Value <= 'f')) then Result := Ord(Value) - Ord('a') + 10
else if ((Value >= 'A') and (Value <= 'F')) then Result := Ord(Value) - Ord('A') + 10
else if ((Value >= '0') and (Value <= '9')) then Result := Ord(Value) - Ord('0')
else Result := -1;
end;
function DigitsToBinStr(var S: AnsiString; Convert: Boolean = True): Boolean;
var
I, J, K: Integer;
T: AnsiString;
begin
// check and convert text characters to hex values 0..15
Result := True;
if Convert then
SetLength(T, Length(S));
J := 0;
for I := 1 to Length(S) do if not CharInSetEx(S[I], [#9, #32]) then
begin
K := DigitToBin(S[I]);
if K >= 0 then
begin
if Convert then
begin
Inc(J);
T[J] := AnsiChar(K)
end;
end else
begin
Result := False;
Break;
end;
end;
if Result and Convert then
begin
SetLength(T, J);
S := T;
end;
end;
function BinStrToBinary(const S: AnsiString): AnsiString;
var
I, J, L: Integer;
B1, B2: Byte;
begin
L := Length(S);
Result := '';
if L > 0 then
begin
SetLength(Result, DivUp(L, 2));
if L = 1 then
Result := S
else
begin
J := 1;
for I := 1 to Length(Result) do
begin
B1 := Byte(S[J]); Inc(J);
if J <= L then
begin
B2 := Byte(S[J]); Inc(J);
end else
B2 := 0;
Result[I] := AnsiChar(B1 shl 4 + B2);
end;
end;
end;
end;
function BinaryToDigits(Buffer: PBytes; SelStart, SelEnd: TKHexEditorSelection): AnsiString;
var
I, J: Integer;
S: AnsiString;
begin
Result := '';
S := '%s' + cFmtText;
for I := SelStart.Index to SelEnd.Index do
begin
Result := AnsiString(Format(string(S), [Result, Buffer[I]]));
if I = SelStart.Index then
begin
for J := 0 to SelStart.Digit - 1 do
Delete(Result, 1, 1);
end;
if I = SelEnd.Index then
begin
for J := SelEnd.Digit to cDigitCount - 1 do
Delete(Result, Length(Result), 1);
end;
end;
end;
function BinaryToText(Buffer: PBytes; SelStart, SelEnd: Integer;
CharMapping: PKEditCharMapping): AnsiString;
var
I: Integer;
begin
if SelEnd > SelStart then
begin
SetLength(Result, SelEnd - SelStart);
System.Move(Buffer[SelStart], Result[1], SelEnd - SelStart);
if CharMapping <> nil then
for I := 1 to Length(Result) do
Result[I] := CharMapping^[Byte(Result[I])];
end else
Result := '';
end;
function ReplaceDigit(Value, Digit, Pos: Integer): Integer;
var
I, Mask, O: Integer;
begin
O := 1;
for I := Pos to cDigitCount - 2 do
O := O * cBase;
Mask := cBase - 1;
Result := (((Value div O) and not Mask) + (Digit and Mask)) * O + Value mod O;
end;
function OppositeReason(ItemReason: TKHexEditorChangeReason): TKHexEditorChangeReason;
begin
case ItemReason of
crDeleteChar: Result := crInsertChar;
crDeleteDigits: Result := crInsertDigits;
crDeleteString: Result := crInsertString;
crInsertChar: Result := crDeleteChar;
crInsertDigits: Result := crDeleteDigits;
crInsertString: Result := crDeleteString;
else
Result := ItemReason;
end;
end;
{ TKHexEditorColors }
constructor TKHexEditorColors.Create(AOwner: TKCustomHexEditor);
begin
FOwner := AOwner;
Initialize;
ClearBrightColors;
end;
procedure TKHexEditorColors.Assign(Source: TPersistent);
begin
if Source is TKHexEditorColors then
begin
Colors := TKHexEditorColors(Source).Colors;
FOwner.Invalidate;
end
else
inherited;
end;
procedure TKHexEditorColors.ClearBrightColors;
var
I: TKHexEditorColorIndex;
begin
for I := 0 to Length(FBrightColors) - 1 do
FBrightColors[I] := clNone;
end;
function TKHexEditorColors.GetColor(Index: TKHexEditorColorIndex): TColor;
const
AreaBkGndSet = [ciAddressBkgnd, ciDigitBkGnd, ciTextBkGnd];
BkGndSet = [ciAddressBkgnd, ciBkGnd, ciDigitBkGnd, ciInactiveCaretBkGnd,
ciInactiveCaretSelBkGnd, ciSelBkGnd, ciSelBkGndFocused, ciTextBkgnd];
begin
case FColorScheme of
ecsGrayed: if Index in BkGndSet then Result := clWindow else Result := clGrayText;
ecsBright:
begin
if FBrightColors[Index] = clNone then
FBrightColors[Index] := BrightColor(FColors[Index], 0.5, bsOfTop);
if FSingleBkGnd and (Index in AreaBkGndSet) then
Result := FBrightColors[ciBkGnd]
else
Result := FBrightColors[Index];
end;
ecsGrayScale: Result := ColorToGrayScale(FColors[Index]);
else
if FSingleBkGnd and (Index in AreaBkGndSet) then
Result := FColors[ciBkGnd]
else
Result := FColors[Index];
end;
end;
function TKHexEditorColors.GetColorData(Index: TKHexEditorColorIndex): TKHexEditorColorData;
var
ColorSpec: TKHexEditorColorSpec;
begin
Result.Index := Index;
Result.Color := FColors[Index];
ColorSpec := GetColorSpec(Index);
Result.Default := ColorSpec.Def;
Result.Name := ColorSpec.Name;
end;
function TKHexEditorColors.GetColorEx(Index: TKHexEditorColorIndex): TColor;
begin
Result := FColors[Index];
end;
function TKHexEditorColors.GetColorName(Index: TKHexEditorColorIndex): string;
begin
Result := GetColorSpec(Index).Name;
end;
procedure TKHexEditorColors.Initialize;
var
I: TKHexEditorColorIndex;
begin
SetLength(FColors, ciHexEditorColorsMax + 1);
SetLength(FBrightColors, ciHexEditorColorsMax + 1);
for I := 0 to Length(FColors) - 1 do
FColors[I] := GetColorSpec(I).Def;
end;
procedure TKHexEditorColors.SetColor(Index: TKHexEditorColorIndex; Value: TColor);
begin
if FColors[Index] <> Value then
begin
FColors[Index] := Value;
FBrightColors[Index] := clNone;
if not (csLoading in FOwner.ComponentState) and FOwner.HandleAllocated then
FOwner.Invalidate;
end;
end;
procedure TKHexEditorColors.SetColorEx(Index: TKHexEditorColorIndex; Value: TColor);
begin
if FColors[Index] <> Value then
begin
FColors[Index] := Value;
FBrightColors[Index] := clNone;
end;
end;
procedure TKHexEditorColors.SetColors(const Value: TKColorArray);
begin
FColors := Value;
ClearBrightColors;
end;
{ TKHexEditorChangeList }
constructor TKHexEditorChangeList.Create(AEditor: TKCustomHexEditor;
RedoList: TKHexEditorChangeList);
begin
inherited Create;
FEditor := AEditor;
FGroupUseLock := 0;
FLimit := cUndoLimitDef;
FIndex := -1;
FModifiedIndex := FIndex;
FRedoList := RedoList;
FOnChange := nil;
end;
procedure TKHexEditorChangeList.AddChange(ItemReason: TKHexEditorChangeReason;
const Data: AnsiString; Inserted: Boolean);
var
P: PKHexEditorChangeItem;
begin
// don't allow succesive crCaretPos
if (ItemReason = crCaretPos) and not Inserted and (FIndex >= 0) and
(PKHexEditorChangeItem(Items[FIndex]).ItemReason = crCaretPos) then
Exit;
if FIndex < FLimit - 1 then
begin
if FIndex < Count - 1 then
Inc(FIndex)
else
FIndex := Add(New(PKHexEditorChangeItem));
P := Items[FIndex];
if FGroupUseLock > 0 then
begin
P.Group := FGroup;
P.GroupReason := FGroupReason;
end else
begin
P.Group := 0;
P.GroupReason := ItemReason;
end;
P.ItemReason := ItemReason;
P.EditArea := FEditor.EditArea;
P.SelEnd := FEditor.SelEnd;
P.SelStart := FEditor.SelStart;
P.Data := Data;
P.Inserted := Inserted;
if FRedoList <> nil then
FRedoList.Clear;
if Assigned(FOnChange) then
FOnChange(Self, ItemReason);
end;
end;
procedure TKHexEditorChangeList.BeginGroup(GroupReason: TKHexEditorChangeReason);
begin
if FGroupUseLock = 0 then
begin
FGroupReason := GroupReason;
Inc(FGroup);
if FGroup = 0 then Inc(FGroup);
end;
Inc(FGroupUseLock);
end;
function TKHexEditorChangeList.CanPeek: Boolean;
begin
Result := FIndex >= 0;
end;
procedure TKHexEditorChangeList.Clear;
begin
inherited;
FGroupUseLock := 0;
FIndex := -1;
FModifiedIndex := FIndex;
end;
procedure TKHexEditorChangeList.EndGroup;
begin
if FGroupUseLock > 0 then
Dec(FGroupUseLock);
end;
function TKHexEditorChangeList.GetModified: Boolean;
function CaretPosOnly: Boolean;
var
I: Integer;
begin
Result := True;
for I := FModifiedIndex + 1 to FIndex do
begin
if PKHexEditorChangeItem(Items[I]).ItemReason <> crCaretPos then
begin
Result := False;
Exit;
end;
end;
end;
begin
Result := (FIndex > FModifiedIndex) and not CaretPosOnly;
end;
procedure TKHexEditorChangeList.Notify(Ptr: Pointer; Action: TListNotification);
var
P: PKHexEditorChangeItem;
begin
case Action of
lnDeleted:
if Ptr <> nil then
begin
P := Ptr;
Dispose(P);
end;
end;
end;
function TKHexEditorChangeList.PeekItem: PKHexEditorChangeItem;
begin
if CanPeek then
begin
Result := Items[FIndex];
Dec(FIndex);
end else
Result := nil;
end;
procedure TKHexEditorChangeList.PokeItem;
begin
if FIndex < Count - 1 then
Inc(FIndex);
end;
procedure TKHexEditorChangeList.SetGroupData(Group: Integer;
GroupReason: TKHexEditorChangeReason);
begin
FGroup := Group;
FGroupReason := GroupReason;
FGroupUseLock := 1;
end;
procedure TKHexEditorChangeList.SetLimit(Value: Integer);
begin
if Value <> FLimit then
begin
FLimit := MinMax(Value, cUndoLimitMin, cUndoLimitMax);
while Count > FLimit do
Delete(0);
FIndex := Min(FIndex, FLimit - 1);
end;
end;
procedure TKHexEditorChangeList.SetModified(Value: Boolean);
begin
if not Value then
FModifiedIndex := FIndex;
end;
{ TKCustomHexEditor }
constructor TKCustomHexEditor.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Color := clWindow;
ControlStyle := [csOpaque, csClickEvents, csDoubleClicks, csCaptureMouse];
Font.Name := cFontNameDef;
Font.Style := cFontStyleDef;
Font.Size := cFontSizeDef;
Font.Pitch := fpFixed;
Font.OnChange := FontChange;
Height := cHeight;
ParentColor := False;
ParentFont := False;
TabStop := True;
Width := cWidth;
FAddressCursor := cAddressCursorDef;
FAddressMode := cAddressModeDef;
FAddressOffset := cAddressOffsetDef;
FAddressPrefix := cAddressPrefixDef;
FAddressSize := cAddressSizeDef;
FAreaSpacing := cAreaSpacingDef;
FBuffer := nil;
{$IFNDEF FPC}
FClipBoardFormat := RegisterClipboardFormat('Any binary data');
{$ENDIF}
FColors := TKHexEditorColors.Create(Self);
FCharHeight := 8;
FCharMapping := DefaultCharMapping;
FCharSpacing := cCharSpacingDef;
FCharWidth := 6;
FDigitGrouping := cDigitGroupingDef;
FDisabledDrawStyle := cDisabledDrawStyleDef;
FDrawStyles := cDrawStylesDef;
FEditArea := eaDigits;
FLeftChar := 0;
FLineHeightPercent := cLineHeightPercentDef;
FLineSize := cLineSizeDef;
FMouseWheelAccumulator := 0;
FOptions := [eoGroupUndo];
FKeyMapping := CreateDefaultKeyMapping;
FRedoList := TKHexEditorChangeList.Create(Self, nil);
FScrollBars := ssBoth;
FScrollSpeed := cScrollSpeedDef;
FScrollTimer := TTimer.Create(Self);
FScrollTimer.Enabled := False;
FScrollTimer.Interval := FScrollSpeed;
FScrollTimer.OnTimer := ScrollTimerHandler;
FSelStart := MakeSelection(0, 0);
FSelEnd := MakeSelection(0, 0);
FStates := [];
FTopLine := 0;
FTotalCharSpacing := 0;
FUndoList := TKHexEditorChangeList.Create(Self, FRedoList);
FUndoList.OnChange := UndoChange;
FOnChange := nil;
FOnReplaceText := nil;
UpdateCharMetrics;
end;
destructor TKCustomHexEditor.Destroy;
begin
inherited;
FOnChange := nil;
FColors.Free;
FUndoList.Free;
FRedoList.Free;
FreeMem(FBuffer);
FBuffer := nil;
end;
procedure TKCustomHexEditor.AddUndoCaretPos(Force: Boolean);
begin
FUndoList.AddChange(crCaretPos, '', Force);
end;
procedure TKCustomHexEditor.AddUndoByte(ItemReason: TKHexEditorChangeReason; Data: Byte;
Inserted: Boolean = True);
begin
FUndoList.AddChange(ItemReason, AnsiChar(Data), Inserted);
end;
procedure TKCustomHexEditor.AddUndoBytes(ItemReason: TKHexEditorChangeReason;
Data: PBytes; Length: Integer; Inserted: Boolean = True);
var
S: AnsiString;
begin
if Length > 0 then
begin
SetLength(S, Length);
Move(Data^, S[1], Length);
FUndoList.AddChange(ItemReason, S, Inserted);
end;
end;
procedure TKCustomHexEditor.AddUndoString(ItemReason: TKHexEditorChangeReason;
const S: AnsiString; Inserted: Boolean = True);
begin
if S <> '' then
FUndoList.AddChange(ItemReason, S, Inserted);
end;
procedure TKCustomHexEditor.Append(At: Integer; Data: TDataSize);
var
S: AnsiString;
begin
if (Data.Size > 0) and (Data.Data <> nil) then
begin
SetString(S, PAnsiChar(Data.Data), Data.Size);
InsertString(At, S, Data.Size);
end;
end;
procedure TKCustomHexEditor.Append(At: Integer; const Data: AnsiString);
begin
InsertString(At, Data, Length(Data));
end;
procedure TKCustomHexEditor.Assign(Source: TPersistent);
begin
if Source is TKCustomHexEditor then with Source as TKCustomHexEditor do
begin
Self.AddressCursor := AddressCursor;
Self.AddressMode := AddressMode;
Self.AddressPrefix := AddressPrefix;
Self.AddressSize := AddressSize;
Self.Align := Align;
Self.Anchors := Anchors;
Self.AutoSize := AutoSize;
Self.BiDiMode := BiDiMode;
Self.BorderStyle := BorderStyle;
Self.BorderWidth := BorderWidth;
Self.CharSpacing := CharSpacing;
Self.Color := Color;
Self.Colors := Colors;
Self.Constraints.Assign(Constraints);
{$IFNDEF FPC}
Self.Ctl3D := Ctl3D;
{$ENDIF}
Self.Data := Data;
Self.DigitGrouping := DigitGrouping;
Self.DisabledDrawStyle := DisabledDrawStyle;
Self.DragCursor := DragCursor;
Self.DragKind := DragKind;
Self.DragMode := DragMode;
Self.DrawStyles := DrawStyles;
Self.EditArea := EditArea;
Self.Enabled := Enabled;
Self.Font := Font;
{$IFNDEF FPC}
Self.ImeMode := ImeMode;
Self.ImeName := ImeName;
{$ENDIF}
Self.LineHeightPercent := LineHeightPercent;
Self.LineSize := LineSize;
Self.Modified := False;
Self.Options := Options;
Self.ParentBiDiMode := ParentBiDiMode;
Self.ParentColor := ParentColor;
{$IFNDEF FPC}
Self.ParentCtl3D := ParentCtl3D;
{$ENDIF}
Self.ParentFont := ParentFont;
Self.ParentShowHint := ParentShowHint;
Self.PopupMenu := PopupMenu;
Self.ScrollBars := ScrollBars;
Self.SelEnd := SelEnd;
Self.SelStart := SelStart;
Self.SetCharMapping(GetCharMapping);
Self.SetKeyMapping(GetKeyMapping);
Self.ShowHint := ShowHint;
Self.TabOrder := TabOrder;
Self.TabStop := TabStop;
Self.Visible := Visible;
end
else
inherited;
end;
procedure TKCustomHexEditor.BeginUndoGroup(GroupReason: TKHexEditorChangeReason);
begin
FUndoList.BeginGroup(GroupReason);
end;
procedure TKCustomHexEditor.BufferChanged;
begin
FUndoList.Clear;
FRedoList.Clear;
UpdateScrollRange;
SelectionChanged(False);
DoChange;
end;
function TKCustomHexEditor.CanScroll(Command: TKEditCommand): Boolean;
var
XMax, YMax: Integer;
P: TPoint;
AD: TKHExEditorAreaDimensions;
begin
AD := GetAreaDimensions;
XMax := GetMaxLeftChar(AD.TotalHorz);
YMax := GetMaxTopLine(AD.TotalVert);
case Command of
ecScrollUp: Result := FTopLine > 0;
ecScrollDown: Result := FTopLine < YMax;
ecScrollLeft: Result := FLeftChar > 0;
ecScrollRight: Result := FLeftChar < XMax;
ecScrollCenter:
begin
P := SelToPoint(FSelEnd, FEditArea);
P.X := P.X - ClientWidth div 2;
P.Y := P.Y - ClientHeight div 2;
Result := (FLeftChar > 0) and (P.X < 0) or (FLeftChar < XMax) and (P.X > FCharWidth) or
(FTopLine > 0) and (P.Y < 0) or (FTopLine < YMax) and (P.Y > FCharHeight);
end;
else
Result := False;
end;
end;
function TKCustomHexEditor.CaretInView: Boolean;
begin
Result := PtInRect(GetModifiedClientRect, SelToPoint(FSelEnd, FEditArea));
end;
procedure TKCustomHexEditor.Clear;
begin
if FBuffer <> nil then
begin
FreeMem(FBuffer);
FBuffer := nil;
FSize := 0;
BufferChanged;
end;
end;
procedure TKCustomHexEditor.ClearChar(At: Integer);
begin
ClearString(At, 1);
end;
procedure TKCustomHexEditor.ClearDigitSelection;
begin
FSelStart.Digit := 0;
FSelEnd.Digit := 0;
end;
procedure TKCustomHexEditor.ClearString(At, Size: Integer);
begin
if (FBuffer <> nil) and (Size > 0) and (At >= 0) and (At + Size <= FSize) then
begin
Move(FBuffer[At + Size], FBuffer[At], (FSize - At - Size) * SizeOf(Byte));
Dec(FSize, Size);
ReallocMem(FBuffer, FSize);
UpdateScrollRange;
Invalidate;
end;
end;
procedure TKCustomHexEditor.ClearUndo;
begin
FUndoList.Clear;
FRedoList.Clear;
end;
procedure TKCustomHexEditor.CMEnabledChanged(var Msg: TLMessage);
begin
inherited;
UpdateEditorCaret;
Invalidate;
end;
procedure TKCustomHexEditor.CMSysColorChange(var Msg: TLMessage);
begin
inherited;
FColors.ClearBrightColors;
end;
function TKCustomHexEditor.CommandEnabled(Command: TKEditCommand): Boolean;
var
L: TKHexEditorSelection;
begin
if Enabled and Visible and not (csDesigning in ComponentState) then
begin
L := SelLength;
case Command of
// movement commands
ecLeft, ecSelLeft: Result := (FSelEnd.Index > 0) or (FEditArea = eaDigits) and (FSelEnd.Digit > 0);
ecRight, ecSelRight: Result := (FEditArea <> eaNone) and (FSelEnd.Index < FSize);
ecUp, ecSelUp: Result := FSelEnd.Index >= FLineSize;
ecDown, ecSelDown: Result := (FEditArea <> eaNone) and (FSelEnd.Index < FSize);
ecLineStart, ecSelLineStart: Result := (FEditArea <> eaNone) and (FSelEnd.Index mod FLineSize > 0);
ecLineEnd, ecSelLineEnd: Result := (FEditArea <> eaNone) and (FSelEnd.Index mod FLineSize < Min(FLineSize - 1, FSize));
ecPageUp, ecSelPageUp: Result := FSelEnd.Index >= FlineSize;
ecPageDown, ecSelPageDown: Result := (FEditArea <> eaNone) and (FSelEnd.Index < FSize div FLineSize * FLineSize);
ecPageLeft, ecSelPageLeft: Result := (FEditArea <> eaNone) and (GetPageHorz > 0) and (FSelEnd.Index mod FLineSize > 0);
ecPageRight, ecSelPageRight: Result := (FEditArea <> eaNone) and (GetPageHorz > 0) and (FSelEnd.Index mod FLineSize < Min(FLineSize - 1, FSize));
ecPageTop, ecSelPageTop: Result := (FEditArea <> eaNone) and (FSelEnd.Index > 0) and (SelToPoint(MakeSelection(FSelEnd.Index, 0), FEditArea).Y div FCharHeight <> 0);
ecPageBottom, ecSelPageBottom: Result := (FEditArea <> eaNone) and (FSelEnd.Index < FSize) and ((ClientHeight - SelToPoint(MakeSelection(FSelEnd.Index, 0), FEditArea).Y) div FCharHeight - 1 <> 0);
ecEditorTop, ecSelEditorTop: Result := FSelEnd.Index > 0;
ecEditorBottom, ecSelEditorBottom: Result := (FEditArea <> eaNone) and (FSelEnd.Index < FSize);
ecGotoXY, ecSelGotoXY: Result := True;
// scroll commands
ecScrollUp, ecScrollDown, ecScrollLeft, ecScrollRight, ecScrollCenter: Result := CanScroll(Command);
// editing commands
ecUndo: Result := not ReadOnly and FUndoList.CanPeek;
ecRedo: Result := not ReadOnly and FRedoList.CanPeek;
ecCopy, ecCut: Result := not Empty and (not ReadOnly or (Command = ecCopy)) and ((L.Index <> 0) or (L.Digit <> 0));
ecPaste: Result := not ReadOnly and (FEditArea <> eaNone) and (ClipBoard.FormatCount > 0);
ecInsertChar: Result := not ReadOnly and (FEditArea <> eaNone);
ecInsertDigits: Result := not ReadOnly and (FEditArea = eaDigits);
ecInsertString: Result := not ReadOnly and (FEditArea <> eaNone);
ecDeleteLastChar: Result := not (Empty or ReadOnly) and (FEditArea <> eaNone) and ((L.Index > 0) or (FSelEnd.Index > 0));
ecDeleteChar: Result := not (Empty or ReadOnly) and (FEditArea <> eaNone) and ((L.Index > 0) or (FSelEnd.Index < FSize));
ecDeleteBOL: Result := not (Empty or ReadOnly) and (FEditArea <> eaNone) and ((L.Index > 0) or (FSelEnd.Index mod FLineSize > 0));
ecDeleteEOL: Result := not (Empty or ReadOnly) and (FEditArea <> eaNone) and ((L.Index > 0) or (FSelEnd.Index mod FLineSize < Min(FLineSize, FSize)));
ecDeleteLine: Result := not (Empty or ReadOnly) and (FEditArea <> eaNone) and ((L.Index > 0) or (FSelEnd.Index mod FLineSize > 0) or (FSelEnd.Index < FSize));
ecSelectAll: Result := not (Empty or ReadOnly) and (FEditArea <> eaNone);
ecClearAll: Result := not (Empty or ReadOnly) and (FEditArea <> eaNone);
ecClearIndexSelection, ecClearSelection: Result := not (Empty or ReadOnly) and (FEditArea <> eaNone) and (L.Index > 0);
ecSearch: Result := not Empty;
ecReplace: Result := not (Empty or ReadOnly);
ecInsertMode: Result := elOverwrite in FStates;
ecOverwriteMode: Result := not (elOverwrite in FStates);
else
Result := True;
end;
end else
Result := False;
end;
procedure TKCustomHexEditor.CreateParams(var Params: TCreateParams);
begin
inherited;
with Params do
begin
if FScrollBars in [ssVertical, ssBoth] then Style := Style or WS_VSCROLL;
if FScrollBars in [ssHorizontal, ssBoth] then Style := Style or WS_HSCROLL;
end;
end;
procedure TKCustomHexEditor.CreateWnd;
begin
inherited;
{$IFDEF USE_WINAPI}
if (eoDropFiles in FOptions) and not (csDesigning in ComponentState) then
DragAcceptFiles(Handle, TRUE);
{$ENDIF}
end;
procedure TKCustomHexEditor.DestroyWnd;
begin
{$IFDEF USE_WINAPI}
if (eoDropFiles in FOptions) and not (csDesigning in ComponentState) then
DragAcceptFiles(Handle, FALSE);
{$ENDIF}
inherited;
end;
procedure TKCustomHexEditor.DoChange;
begin
if Assigned(FOnChange) then
FOnChange(Self);
end;
function TKCustomHexEditor.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
MousePos: TPoint): Boolean;
const
WHEEL_DIVISOR = 120;
var
LinesToScroll, WheelClicks: Integer;
begin
Result := inherited DoMouseWheel(Shift, WheelDelta, MousePos);
if not Result then
begin
if ssCtrl in Shift then
LinesToScroll := GetModifiedClientRect.Bottom div FCharHeight
else
LinesToScroll := 3;
Inc(FMouseWheelAccumulator, WheelDelta);
WheelClicks := FMouseWheelAccumulator div WHEEL_DIVISOR;
FMouseWheelAccumulator := FMouseWheelAccumulator mod WHEEL_DIVISOR;
ScrollBy(0, - WheelClicks * LinesToScroll, True);
Result := True;
end;
end;
procedure TKCustomHexEditor.EditAreaChanged;
begin
if FEditArea = eaNone then
FEditArea := eaDigits;
if not (edAddress in FDrawStyles) and (FEditArea = eaAddress) then
FEditArea := eaDigits;
if not (edDigits in FDrawStyles) and (FEditArea = eaDigits) then
FEditArea := eaText;
if not (edText in FDrawStyles) and (FEditArea = eaText) then
if edDigits in FDrawStyles then
FEditArea := eaDigits
else
FEditArea := eaNone;
end;
procedure TKCustomHexEditor.EndUndoGroup;
begin
FUndoList.EndGroup;
end;
function TKCustomHexEditor.ExecuteCommand(Command: TKEditCommand;
Data: Pointer): Boolean;
var
I, J, K, M, N, O: Integer;
CanInsert, MoreBytes, Found, MatchCase: Boolean;
C1, C2, C3: AnsiChar;
S, S_FirstChar, S_LastChar, T: AnsiString;
P: TPoint;
Area: TKHexEditorArea;
L, OldSelStart, OldSelEnd, Sel1, Sel2: TKHexEditorSelection;
PChI, PChI_First, PChI_Next: PKHexEditorChangeItem;
PSD: PKEditSearchData;
ReplaceAction: TKEditReplaceAction;
{$IFNDEF FPC}
BA: PBytes;
H: THandle;
{$ENDIF}
begin
Result := False;
if CommandEnabled(Command) then
begin
Result := True;
L := SelLength;
OldSelEnd := FSelEnd;
OldSelStart := FSelStart;
case Command of
ecLeft..ecSelGotoXY: AddUndoCaretPos(False);
end;
case Command of
ecLeft, ecSelLeft:
begin
InternalMoveLeft;
SelectionChanged(Command <> ecSelLeft);
end;
ecRight, ecSelRight:
begin
InternalMoveRight;
SelectionChanged(Command <> ecSelRight);
end;
ecUp, ecSelUp:
begin
Dec(FSelEnd.Index, FLineSize);
SelectionChanged(Command <> ecSelUp);
end;
ecDown, ecSelDown:
begin
Inc(FSelEnd.Index, FLineSize);
SelectionChanged(Command <> ecSelDown);
end;
ecLineStart, ecSelLineStart:
begin
FSelEnd := MakeSelection((FSelEnd.Index div FLineSize) * FLineSize, 0);
SelectionChanged(Command <> ecSelLineStart);
end;
ecLineEnd, ecSelLineEnd:
begin
FSelEnd := MakeSelection((FSelEnd.Index div FLineSize) * FLineSize + FLineSize - 1, cDigitCount - 1);
SelectionChanged(Command <> ecSelLineEnd);
end;
ecPageUp, ecSelPageUp:
begin
Dec(FSelEnd.Index, Min(ClientHeight div FCharHeight, FSelEnd.Index div FLineSize) * FLineSize);
SelectionChanged(Command <> ecSelPageUp);
end;
ecPageDown, ecSelPageDown:
begin
Inc(FSelEnd.Index, Min(ClientHeight div FCharHeight, (FSize - FSelEnd.Index) div FLineSize) * FLineSize);
SelectionChanged(Command <> ecSelPageDown);
end;
ecPageLeft, ecSelPageLeft:
begin
Dec(FSelEnd.Index, Min(GetPageHorz, FSelEnd.Index mod FLineSize));
SelectionChanged(Command <> ecSelPageLeft);
end;
ecPageRight, ecSelPageRight:
begin
Inc(FSelEnd.Index, Min(GetPageHorz, FLineSize - 1 - FSelEnd.Index mod FLineSize));
SelectionChanged(Command <> ecSelPageRight);
end;
ecPageTop, ecSelPageTop:
begin
P := SelToPoint(MakeSelection(FSelEnd.Index, 0), FEditArea);
Dec(FSelEnd.Index, P.Y div FCharHeight * FLineSize);
SelectionChanged(Command <> ecSelPageTop);
end;
ecPageBottom, ecSelPageBottom:
begin
P := SelToPoint(MakeSelection(FSelEnd.Index, 0), FEditArea);
Inc(FSelEnd.Index, ((ClientHeight - P.Y) div FCharHeight - 1) * FLineSize);
SelectionChanged(Command <> ecSelPageBottom);
end;
ecEditorTop, ecSelEditorTop:
begin
FSelEnd := MakeSelection(0, 0);
SelectionChanged(Command <> ecSelEditorTop);
end;
ecEditorBottom, ecSelEditorBottom:
begin
FSelEnd := MakeSelection(FSize, 0);
SelectionChanged(Command <> ecSelEditorBottom);
end;
ecGotoXY, ecSelGotoXY:
begin
Sel1 := PointToSel(PPoint(Data)^, False, Area);
if Area <> eaNone then
begin
FSelEnd := Sel1;
FEditArea := Area;
SelectionChanged(Command <> ecSelGotoXY);
end else
Result := False;
end;
// scroll commands
ecScrollUp:
begin
if (FEditArea <> eaNone) and (SelToPoint(FSelEnd, FEditArea).Y >= GetModifiedClientRect.Bottom - FCharHeight) then
begin
ScrollBy(0, -1, False);
Dec(FSelEnd.Index, FLineSize);
SelectionChanged(True, False);
Invalidate;
end else
ScrollBy(0, -1, True);
end;
ecScrollDown:
begin
if (FEditArea <> eaNone) and (SelToPoint(FSelEnd, FEditArea).Y <= GetModifiedClientRect.Top) then
begin
ScrollBy(0, 1, False);
Inc(FSelEnd.Index, FLineSize);
SelectionChanged(True, False);
Invalidate;
end else
ScrollBy(0, 1, True);
end;
ecScrollLeft:
begin
if FEditArea <> eaNone then
begin
// overscroll check
P := SelToPoint(MakeSelection(0, 0), FEditArea);
if P.X < GetModifiedClientRect.Right - FCharWidth then
begin
ScrollBy(-1, 0, True);
P := SelToPoint(FSelEnd, FEditArea);
if (P.X >= GetModifiedClientRect.Right) and ((FSelEnd.Index mod FLineSize > 0) or (FSelEnd.Digit > 0)) then
ExecuteCommand(ecLeft)
end;
end else
ScrollBy(-1, 0, True);
end;
ecScrollRight:
begin
if FEditArea <> eaNone then
begin
// overscroll check
P := SelToPoint(MakeSelection(FLineSize - 1, cDigitCount - 1), FEditArea);
if P.X > 0 then
begin
ScrollBy(1, 0, True);
P := SelToPoint(FSelEnd, FEditArea);
if (P.X < 0) and ((FSelEnd.Index mod FLineSize < FLineSize - 1) or (FSelEnd.Digit < cDigitCount - 1)) then
ExecuteCommand(ecRight)
end;
end else
ScrollBy(1, 0, True);
end;
ecScrollCenter:
begin
P := SelToPoint(FSelEnd, FEditArea);
I := (P.X - ClientWidth div 2) div FCharWidth;
J := (P.Y - ClientHeight div 2) div FCharHeight;
ScrollBy(I, J, True);
end;
// editing commands
ecUndo:
begin
PChI := FUndoList.PeekItem;
PChI_First := PChI;
while PChI <> nil do
begin
I := Length(PChI.Data);
J := Min(I, FSize - PChI.SelEnd.Index);
FRedoList.SetGroupData(PChI.Group, PChI.GroupReason);
case PChI.ItemReason of
crCaretPos:
FRedoList.AddChange(crCaretPos, '');
crDeleteChar, crDeleteDigits, crDeleteString:
begin
if FBuffer <> nil then
begin
SetLength(S, J);
System.Move(FBuffer[PChI.SelEnd.Index], S[1], J);
end else
S := '';
FRedoList.AddChange(OppositeReason(PChI.ItemReason), S, PChI.Inserted);
end;
crInsertChar, crInsertDigits, crInsertString:
FRedoList.AddChange(OppositeReason(PChI.ItemReason), PChI.Data);
end;
FSelEnd := PChI.SelEnd;
FSelStart := PChI.SelStart;
FEditArea := PChI.EditArea;
case PChI.ItemReason of
crDeleteChar, crDeleteDigits, crDeleteString:
begin
if PChI.Inserted then
ClearString(PChI.SelEnd.Index, I)
else if FBuffer <> nil then
begin
System.Move(PChI.Data[1], FBuffer[PChI.SelEnd.Index], J);
Invalidate;
end;
end;
crInsertChar, crInsertDigits, crInsertString:
InsertString(GetRealSelStart.Index, PChI.Data, I);
end;
EditAreaChanged;
SelectionChanged(False, False);
if PChI.ItemReason <> crCaretPos then
DoChange;
PChI_Next := FUndoList.PeekItem;
if (PChI_Next <> nil) and not ((PChI.Group <> 0) and (PChI.Group = PChI_Next.Group) or
(eoGroupUndo in FOptions) and (PChI_First.GroupReason = PChI_Next.GroupReason)) then
begin
FUndoList.PokeItem;
Break;
end;
PChI := PChI_Next;
end;
if not CaretInView then
ExecuteCommand(ecScrollCenter);
end;
ecRedo:
begin
PChI := FRedoList.PeekItem;
PChI_First := PChI;
while PChI <> nil do
begin
FUndoList.PokeItem;
I := Length(PChI.Data);
Sel1 := GetRealSelStart;
case PChI.ItemReason of
crInsertChar, crInsertDigits, crInsertString:
begin
if PChI.Inserted then
InsertString(Sel1.Index, PChI.Data, I)
else if FBuffer <> nil then
begin
System.Move(PChI.Data[1], FBuffer[Sel1.Index], Min(I, FSize - FSelEnd.Index));
Invalidate;
end;
end;
crDeleteChar, crDeleteDigits, crDeleteString:
ClearString(Sel1.Index, I);
end;
FSelEnd := PChI.SelEnd;
FSelStart := PChI.SelStart;
FEditArea := PChI.EditArea;
EditAreaChanged;
SelectionChanged(False, False);
if PChI.ItemReason <> crCaretPos then
DoChange;
PChI_Next := FRedoList.PeekItem;
if (PChI_Next <> nil) and not ((PChI.Group <> 0) and (PChI.Group = PChI_Next.Group) or
(eoGroupUndo in FOptions) and (PChI_First.GroupReason = PChI_Next.GroupReason)) then
begin
FRedoList.PokeItem;
Break;
end;
PChI := PChI_Next;
end;
if not CaretInView then
ExecuteCommand(ecScrollCenter);
end;
ecCopy:
begin
Sel1 := GetRealSelStart;
Sel2 := GetRealSelEnd;
{$IFDEF FPC}
ClipBoard.AsText := string(BinaryToDigits(FBuffer, Sel1, Sel2))
{$ELSE}
if FEditArea = eaDigits then
ClipBoard.AsText := string(BinaryToDigits(FBuffer, Sel1, Sel2))
else if L.Index <> 0 then
begin
S := BinaryToText(FBuffer, Sel1.Index, Sel2.Index, @FCharMapping);
H := GlobalAlloc(GMEM_MOVEABLE + GMEM_DDESHARE, L.Index);
try
BA := GlobalLock(H);
try
System.Move(FBuffer[Sel1.Index], BA^, L.Index);
finally
GlobalUnlock(H);
end;
ClipBoard.Open;
try
ClipBoard.SetAsHandle(FClipboardFormat, H);
ClipBoard.AsText := string(S);
finally
ClipBoard.Close;
end;
except
GlobalFree(H);
end;
end;
{$ENDIF}
end;
ecCut:
begin
ExecuteCommand(ecCopy);
ExecuteCommand(ecClearSelection);
end;
ecPaste:
begin
if L.Index > 0 then
ExecuteCommand(ecClearSelection);
if ClipBoard.FormatCount > 0 then
begin
S := '';
{$IFNDEF FPC}
H := 0;
// paste as binary data
if ClipBoard.HasFormat(FClipboardFormat) then
H := ClipBoard.GetAsHandle(FClipboardFormat) else
{$ENDIF}
if ClipBoard.HasFormat(CF_TEXT) then
begin
S := AnsiString(ClipBoard.AsText);
if S <> '' then
begin
M := Length(S);
if (FEditArea = eaDigits) and ExecuteCommand(ecInsertDigits, Pointer(S)) then
begin
S := '';
if M >= cDigitCount then
begin
Inc(FSelEnd.Index, M div cDigitCount)
end else
begin
Inc(FSelEnd.Digit, M);
if FSelEnd.Digit >= cDigitCount then
begin
Inc(FSelEnd.Index);
FSelEnd.Digit := FSelEnd.Digit mod cDigitCount;
end;
end;
SelectionChanged(True);
end else
ExecuteCommand(ecInsertString, Pointer(S));
end;
end
{$IFNDEF FPC}
else
H := ClipBoard.GetAsHandle(ClipBoard.Formats[0]);
if H <> 0 then
begin
BA := GlobalLock(H);
try
I := GlobalSize(H);
if I > 0 then
begin
SetLength(S, I);
System.Move(BA^, S[1], I);
end;
finally
GlobalUnlock(H);
end;
if S <> '' then
ExecuteCommand(ecInsertString, Pointer(S));
end
{$ENDIF}
;
if S <> '' then
begin
Inc(FSelEnd.Index, Length(S));
FSelEnd.Digit := 0;
SelectionChanged(True);
end;
end;
end;
ecInsertChar:
begin
BeginUndoGroup(crInsertChar);
try
N := PByte(Data)^;
if L.Index > 0 then
ExecuteCommand(ecClearSelection);
ValidateSelection(FSelEnd, FEditArea);
if FBuffer <> nil then
I := FBuffer[FSelEnd.Index]
else
I := 0;
CanInsert := (FBuffer = nil) or (FSelEnd.Digit = 0) and
(not (elOverwrite in FStates) or (FSelEnd.Index = FSize));
AddUndoByte(crDeleteChar, I, CanInsert);
if CanInsert then
InsertChar(FSelEnd.Index, 0)
else
Invalidate;
case FEditArea of
eaDigits:
begin
FBuffer[FSelEnd.Index] := ReplaceDigit(FBuffer[FSelEnd.Index], N, FSelEnd.Digit);
InternalMoveRight;
end;
eaText:
begin
FBuffer[FSelEnd.Index] := N;
InternalMoveRight;
end;
end;
SelectionChanged(True);
finally
EndUndoGroup;
end;
end;
ecInsertDigits:
begin
S := AnsiString(Data);
if (S <> '') and DigitsToBinStr(S) then
begin
BeginUndoGroup(crInsertDigits);
try
if L.Index > 0 then
ExecuteCommand(ecClearSelection);
ValidateSelection(FSelEnd, FEditArea);
MoreBytes := Length(S) >= cDigitCount;
if MoreBytes then
// we don't move digit positions of the remaining block
SetLength(S, Length(S) div cDigitCount * cDigitCount);
J := 0;
if (FBuffer <> nil) and (not MoreBytes or (FSelEnd.Digit > 0)) then
begin
I := FBuffer[FSelEnd.Index];
S_FirstChar := AnsiChar(I);
S_LastChar := S_FirstChar;
// split current byte
AddUndoByte(crInsertChar, I);
ClearChar(FSelEnd.Index);
N := Length(S);
for I := FSelEnd.Digit to cDigitCount - 1 do
begin
if J < N then
begin
Inc(J);
S_FirstChar := AnsiChar(ReplaceDigit(Ord(S_FirstChar[1]), Ord(S[J]), I));
end else
Break;
end;
K := Length(S);
if K > J then
for I := FSelEnd.Digit - 1 downto 0 do
begin
if K > J then
begin
S_LastChar := AnsiChar(ReplaceDigit(Ord(S_LastChar[1]), Ord(S[K]), I));
Dec(K);
end else
Break;
end
else
S_LastChar := '';
O := cDigitCount;
end else
begin
S_FirstChar := '';
S_LastChar := '';
O := 0;
end;
T := '';
if MoreBytes then
begin
N := Length(S) - O;
O := J;
for I := 0 to N div cDigitCount - 1 do
begin
K := 0;
for J := 1 to cDigitCount do
begin
K := K * cBase;
M := I * 2 + J + O;
Inc(K, Ord(S[M]));
end;
T := AnsiString(Format('%s%s', [T, Char(K)]));
end;
end;
S := S_FirstChar + T + S_LastChar;
// always insert (don't overwrite)
AddUndoString(crDeleteDigits, S);
InsertString(FSelEnd.Index, S, Length(S));
SelectionChanged(True);
finally
EndUndoGroup;
end;
end else
Result := False;
end;
ecInsertString:
begin
S := AnsiString(Data);
if S <> '' then
begin
BeginUndoGroup(crInsertString);
try
if L.Index > 0 then
ExecuteCommand(ecClearIndexSelection);
// always insert (don't overwrite)
AddUndoString(crDeleteString, S);
InsertString(FSelEnd.Index, S, Length(S));
SelectionChanged(True);
finally
EndUndoGroup;
end;
end else
Result := False;
end;
ecDeleteLastChar:
begin
if L.Index <> 0 then ExecuteCommand(ecClearSelection) else
begin
BeginUndoGroup(crDeleteString);
try
AddUndoCaretPos;
FSelStart.Index := FSelEnd.Index - 1;
ExecuteCommand(ecClearIndexSelection)
finally
EndUndoGroup;
end;
end;
end;
ecDeleteChar:
begin
if L.Index <> 0 then ExecuteCommand(ecClearSelection) else
begin
BeginUndoGroup(crDeleteString);
try
AddUndoCaretPos;
FSelStart.Index := FSelEnd.Index + 1;
ExecuteCommand(ecClearIndexSelection)
finally
EndUndoGroup;
end;
end;
end;
ecDeleteBOL:
begin
if L.Index <> 0 then ExecuteCommand(ecClearSelection) else
begin
BeginUndoGroup(crDeleteString);
try
AddUndoCaretPos;
FSelStart.Index := (FSelEnd.Index div FLineSize) * FLineSize;
ExecuteCommand(ecClearIndexSelection)
finally
EndUndoGroup;
end;
end;
end;
ecDeleteEOL:
begin
if L.Index <> 0 then ExecuteCommand(ecClearSelection) else
begin
BeginUndoGroup(crDeleteString);
try
AddUndoCaretPos;
FSelStart.Index := Min((FSelEnd.Index div FLineSize + 1) * FLineSize, FSize);
ExecuteCommand(ecClearIndexSelection)
finally
EndUndoGroup;
end;
end;
end;
ecDeleteLine:
begin
if L.Index <> 0 then ExecuteCommand(ecClearSelection) else
begin
BeginUndoGroup(crDeleteString);
try
AddUndoCaretPos;
FSelStart.Index := (FSelEnd.Index div FLineSize) * FLineSize;
FSelEnd.Index := Min(FSelStart.Index + FLineSize, FSize);
ExecuteCommand(ecClearIndexSelection)
finally
EndUndoGroup;
end;
end;
end;
ecSelectAll:
begin
AddUndoCaretPos;
FSelStart := MakeSelection(0, 0);
FSelEnd := MakeSelection(FSize, 0);
SelectionChanged(False);
end;
ecClearAll:
begin
ExecuteCommand(ecSelectAll);
ExecuteCommand(ecClearIndexSelection);
end;
ecClearIndexSelection:
begin
I := GetRealSelStart.Index;
AddUndoBytes(crInsertString, PBytes(@FBuffer[I]), L.Index, True);
ClearString(I, L.Index);
FSelEnd := MakeSelection(I, 0);
SelectionChanged(True);
end;
ecClearSelection:
begin
Sel1 := GetRealSelStart;
Sel2 := GetRealSelEnd;
if (Sel1.Digit > 0) {and (Sel1.Digit + Sel2.Digit = cDigitCount) }then
begin
BeginUndoGroup(crDeleteDigits);
try
// digit clear mode
AddUndoCaretPos;
FSelEnd := MakeSelection(Sel1.Index + 1, 0);
FSelStart := FSelEnd;
if Sel2.Digit = 0 then
begin
Dec(L.Index);
N := FBuffer[Sel2.Index - 1];
end else
N := FBuffer[Sel2.Index];
AddUndoBytes(crInsertDigits, PBytes(@FBuffer[FSelEnd.Index]), L.Index, True);
ClearString(FSelEnd.Index, L.Index);
FSelEnd := Sel1;
AddUndoByte(crDeleteChar, FBuffer[Sel1.Index], False);
for I := Sel1.Digit to cDigitCount - 1 do
begin
FBuffer[Sel1.Index] := ReplaceDigit(FBuffer[Sel1.Index], N mod cBase, I);
N := N div cBase;
end;
SelectionChanged(True);
finally
EndUndoGroup;
end;
end else
ExecuteCommand(ecClearIndexSelection);
end;
ecSearch, ecReplace:
begin
// doesn't search for single digits
PSD := Data;
if PSD <> nil then
begin
PSD.ErrorReason := eseOk;
S := AnsiString(PSD.TextToFind);
if Command = ecReplace then
begin
T := AnsiString(PSD.TextToReplace);
ReplaceAction := eraYes;
end;
if esoSelectedOnly in PSD.Options then
if esoFirstSearch in PSD.Options then
begin
PSD.SelStart := GetRealSelStart.Index;
PSD.SelEnd := GetRealSelEnd.Index;
end else
begin
PSD.SelStart := MinMax(PSD.SelStart, 0, FSize);
PSD.SelEnd := MinMax(PSD.SelEnd, 0, FSize);
end;
if esoFirstSearch in PSD.Options then
Exclude(PSD.Options, esoWereDigits);
if esoTreatAsDigits in PSD.Options then
begin
if DigitsToBinStr(S) then
begin
S := BinStrToBinary(S);
if Command = ecReplace then
begin
if DigitsToBinStr(T) then
begin
T := BinStrToBinary(T);
PSD.TextToFind := string(S);
PSD.TextToReplace := string(T);
Exclude(PSD.Options, esoTreatAsDigits);
Include(PSD.Options, esoWereDigits);
end else
PSD.ErrorReason := eseNoDigitsReplace;
end else
begin
PSD.TextToFind := string(S);
Exclude(PSD.Options, esoTreatAsDigits);
Include(PSD.Options, esoWereDigits);
end;
end else
PSD.ErrorReason := eseNoDigitsFind;
end;
if PSD.ErrorReason = eseOk then
begin
N := Length(S);
if esoBackwards in PSD.Options then
begin
O := -1;
if (esoEntireScope in PSD.Options) and (esoFirstSearch in PSD.Options) then
I := FSize
else
I := GetRealSelStart.Index - 1;
if esoSelectedOnly in PSD.Options then
begin
M := PSD.SelStart;
if esoFirstSearch in PSD.Options then
I := PSD.SelEnd
end else
M := 0;
I := Min(I, FSize - N);
if I < M then
PSD.ErrorReason := eseNoMatch
end else
begin
O := 1;
if (esoEntireScope in PSD.Options) and (esoFirstSearch in PSD.Options) then
I := 0
else
I := GetRealSelEnd.Index;
if esoSelectedOnly in PSD.Options then
begin
M := PSD.SelEnd;
if esoFirstSearch in PSD.Options then
I := PSD.SelStart
end else
M := FSize;
M := Min(M, FSize - N);
if I >= M then
PSD.ErrorReason := eseNoMatch
end;
if PSD.ErrorReason = eseOk then
begin
Found := False;
MatchCase := PSD.Options * [esoMatchCase, esoWereDigits] <> [];
if MatchCase then
C1 := S[1]
else
C1 := UpCase(S[1]);
I := MinMax(I, 0, FSize - 1);
while I <> M do
begin
if MatchCase then
C2 := AnsiChar(FBuffer[I])
else
C2 := UpCase(AnsiChar(FBuffer[I]));
if C1 = C2 then
begin
if FSize - I >= N then
begin
J := 2;
Dec(I);
while (J <= N) do
begin
if MatchCase then
begin
C2 := AnsiChar(FBuffer[I + J]);
C3 := S[J];
end else
begin
C2 := Upcase(AnsiChar(FBuffer[I + J]));
C3 := Upcase(S[J]);
end;
if C2 = C3 then
Inc(J)
else
Break;
end;
Inc(I);
if J = N + 1 then
begin
Found := True;
FSelStart := MakeSelection(I, 0);
FSelEnd := MakeSelection(I + N, 0);
if Command = ecReplace then
begin
if (esoPrompt in PSD.Options) and Assigned(FOnReplaceText) then
begin
SelectionChanged(False, False);
if not CaretInView then
ExecuteCommand(ecScrollCenter);
FOnReplaceText(Self, string(S), string(T), ReplaceAction)
end else
ReplaceAction := eraYes;
case ReplaceAction of
eraCancel: Break;
eraYes, eraAll:
begin
if T = '' then
ExecuteCommand(ecClearIndexSelection)
else
ExecuteCommand(ecInsertString, Pointer(T));
FSelEnd := MakeSelection(I + Length(T), 0);
AddUndoCaretPos;
if ReplaceAction = eraAll then
Include(PSD.Options, esoAll);
end;
end;
if not (esoAll in PSD.Options) then
Break;
end else
Break;
end
end;
end;
Inc(I, O);
end;
if Found then
begin
SelectionChanged(False, False);
if not CaretInView then
ExecuteCommand(ecScrollCenter);
end else
PSD.ErrorReason := eseNoMatch;
end;
end;
Exclude(PSD.Options, esoFirstSearch);
end else
Result := False;
end;
ecInsertMode:
begin
Exclude(FStates, elOverwrite);
UpdateEditorCaret(True);
end;
ecOverwriteMode:
begin
Include(FStates, elOverwrite);
UpdateEditorCaret(True);
end;
ecToggleMode:
begin
if elOverwrite in FStates then
Exclude(FStates, elOverwrite)
else
Include(FStates, elOverwrite);
UpdateEditorCaret(True);
end;
// focus change
ecGotFocus,
ecLostFocus:
begin
UpdateEditorCaret;
Invalidate;
end;
end;
if (OldSelStart.Index <> OldSelEnd.Index) or (FSelStart.Index <> FSelEnd.Index) or
(OldSelStart.Digit <> OldSelEnd.Digit) or (FSelStart.Digit <> FSelEnd.Digit) or
not (elCaretVisible in FStates) and (edInactiveCaret in FDrawStyles) and
((FSelStart.Index <> OldSelStart.Index) or (FSelStart.Digit <> OldSelStart.Digit) or
(FSelEnd.Index <> OldSelEnd.Index) or (FSelEnd.Digit <> OldSelEnd.Digit)) then
Invalidate;
end;
end;
procedure TKCustomHexEditor.FontChange(Sender: TObject);
begin
if not (csDestroying in ComponentState) then
begin
Font.Pitch := fpFixed;
if Font.Size >= 0 then
Font.Size := MinMax(Font.Size, cFontSizeMin, cFontSizeMax);
UpdateCharMetrics;
UpdateScrollRange;
end;
end;
function TKCustomHexEditor.GetAreaDimensions: TKHexEditorAreaDimensions;
begin
FillChar(Result, SizeOf(Result), 0);
with Result do
begin
if edAddress in FDrawStyles then
begin
Address := Length(FAddressPrefix) + FAddressSize;
if FDrawStyles * [edDigits, edText] <> [] then
AddressOut := FAreaSpacing;
end;
if edDigits in FDrawStyles then
begin
Digits := FLineSize * cDigitCount + FLineSize div FDigitGrouping;
if FLineSize mod FDigitGrouping = 0 then
Dec(Digits);
if edAddress in FDrawStyles then
DigitsIn := FAreaSpacing;
if edText in FDrawStyles then
DigitsOut := FAreaSpacing;
end;
if edText in FDrawStyles then
begin
Text := FLineSize;
if FDrawStyles * [edAddress, edDigits] <> [] then
TextIn := FAreaSpacing;
end;
TotalHorz := Address + AddressOut + Digits + DigitsIn + DigitsOut + Text + TextIn;
if [edAddress, edDigits, edText] * FDrawStyles <> [] then
TotalVert := LineCount
else
TotalVert := 0;
end;
end;
function TKCustomHexEditor.GetCaretVisible: Boolean;
begin
Result := elCaretVisible in FStates;
end;
function TKCustomHexEditor.GetCharMapping: TKEditCharMapping;
begin
Result := FCharMapping;
end;
function TKCustomHexEditor.GetClientHeightChars: Integer;
begin
Result := ClientHeight div FCharHeight;
end;
function TKCustomHexEditor.GetClientWidthChars: Integer;
begin
Result := ClientWidth div FCharWidth;
end;
function TKCustomHexEditor.GetCommandKey(Index: TKEditCommand): TKEditKey;
var
I: Integer;
begin
Result.Key := 0;
Result.Shift := [];
for I := 0 to Length(FKeyMapping) - 1 do
if FKeyMapping[I].Command = Index then
begin
Result := FKeyMapping[I].Key;
Exit;
end;
end;
function TKCustomHexEditor.GetData: TDataSize;
begin
Result.Data := FBuffer;
Result.Size := FSize;
end;
function TKCustomHexEditor.GetEmpty: Boolean;
begin
Result := FBuffer = nil;
end;
function TKCustomHexEditor.GetFirstVisibleIndex: Integer;
begin
Result := PointToSel(Point(0, 0), False, FEditArea).Index;
end;
function TKCustomHexEditor.GetInsertMode: Boolean;
begin
Result := not (elOverwrite in FStates);
end;
function TKCustomHexEditor.GetKeyMapping: TKEditKeyMapping;
begin
Result := FKeyMapping;
end;
function TKCustomHexEditor.GetLastVisibleIndex: Integer;
begin
Result := PointToSel(GetModifiedClientRect.BottomRight, False, FEditArea).Index;
end;
function TKCustomHexEditor.GetLineCount: Integer;
begin
Result := DivUp(FSize + 1, FLineSize);
end;
function TKCustomHexEditor.GetLines(Index: Integer): TDataSize;
var
I: Integer;
begin
I := Index * FLineSize;
if (FBuffer <> nil) and (I >= 0) and (I < FSize) then
begin
Result.Data := @FBuffer[I];
Result.Size := Min(FLineSize, FSize - I);
end else
begin
Result.Data := nil;
Result.Size := 0;
end;
end;
function TKCustomHexEditor.GetModified: Boolean;
begin
Result := (elModified in FStates) or FUndoList.Modified;
end;
function TKCustomHexEditor.GetModifiedClientRect: TRect;
begin
Result := Rect(0, 0, GetClientWidthChars * FCharWidth, GetClientHeightChars * FCharHeight);
end;
function TKCustomHexEditor.GetMaxLeftChar(Extent: Integer): Integer;
begin
if Extent <= 0 then
Extent := GetAreaDimensions.TotalHorz;
Result := Max(Extent - GetClientWidthChars, 0);
end;
function TKCustomHexEditor.GetMaxTopLine(Extent: Integer): Integer;
begin
if Extent <= 0 then
Extent := GetAreaDimensions.TotalVert;
Result := Max(Extent - GetClientHeightChars, 0);
end;
function TKCustomHexEditor.GetPageHorz: Integer;
begin
case FEditArea of
eaDigits: Result := ClientWidth * FDigitgrouping div (FCharWidth * (cDigitCount * FDigitGrouping + 1));
eaText: Result := ClientWidth div FCharWidth;
else
Result := 0;
end;
end;
function TKCustomHexEditor.GetReadOnly: Boolean;
begin
Result := elReadOnly in FStates;
end;
function TKCustomHexEditor.GetRealSelEnd: TKHexEditorSelection;
begin
if FSelStart.Index <= FSelEnd.Index then
Result := FSelEnd
else
Result := FSelStart;
end;
function TKCustomHexEditor.GetRealSelStart: TKHexEditorSelection;
begin
if FSelStart.Index <= FSelEnd.Index then
Result := FSelStart
else
Result := FSelEnd;
end;
function TKCustomHexEditor.GetSelLength: TKHexEditorSelection;
begin
if FSelStart.Index <= FSelEnd.Index then
Result.Index := FSelEnd.Index - FSelStart.Index
else
Result.Index := FSelStart.Index - FSelEnd.Index;
if FSelStart.Digit <= FSelEnd.Digit then
Result.Digit := FSelEnd.Digit - FSelStart.Digit
else
Result.Digit := FSelStart.Digit - FSelEnd.Digit;
end;
function TKCustomHexEditor.GetSelText: TKHexEditorSelText;
var
L, Sel1, Sel2: TKHexEditorSelection;
begin
L := SelLength;
with Result do
begin
if L.Index > 0 then
begin
Sel1 := GetRealSelStart;
Sel2 := GetRealSelEnd;
AsBinaryRaw := BinaryToText(FBuffer, Sel1.Index, Sel2.Index, nil);
AsBinaryMapped := BinaryToText(FBuffer, Sel1.Index, Sel2.Index, @FCharMapping);
AsDigits := BinaryToDigits(FBuffer, Sel1, Sel2);
Sel1.Digit := 0;
Sel2.Digit := 0;
AsDigitsByteAligned := BinaryToDigits(FBuffer, Sel1, Sel2);
end else
begin
AsBinaryRaw := '';
AsBinaryMapped := '';
AsDigits := '';
AsDigitsByteAligned := '';
end;
end;
end;
function TKCustomHexEditor.GetUndoLimit: Integer;
begin
Result := FUndoList.Limit;
end;
function TKCustomHexEditor.HasFocus: Boolean;
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
if (Form <> nil) and Form.Visible and Form.Enabled and Form.Active then
Result := (Form.ActiveControl = Self)
else
Result := False;
end;
procedure TKCustomHexEditor.InsertChar(At: Integer; Value: Byte);
begin
InsertString(At, AnsiChar(Value), 1);
end;
procedure TKCustomHexEditor.InsertString(At: Integer; const Value: AnsiString; Size: Integer);
begin
if (At >= 0) and (At <= FSize) and (Length(Value) > 0) then
begin
Inc(FSize, Size);
ReallocMem(FBuffer, FSize);
if At < FSize - Size then
Move(FBuffer[At], FBuffer[At + Size], (FSize - At - Size) * SizeOf(Byte));
Move(Value[1], FBuffer[At], Size);
UpdateScrollRange;
end;
end;
function TKCustomHexEditor.InternalGetSelAvail: Boolean;
begin
Result := SelAvail;
end;
procedure TKCustomHexEditor.InternalMoveLeft;
begin
if FEditArea = eaDigits then
begin
if FSelEnd.Digit > 0 then
Dec(FSelEnd.Digit)
else if FSelEnd.Index > 0 then
begin
FSelEnd.Digit := cDigitCount - 1;
Dec(FSelEnd.Index);
end
end else
Dec(FSelEnd.Index);
end;
procedure TKCustomHexEditor.InternalMoveRight;
begin
if FEditArea = eaDigits then
begin
if (FSelEnd.Index < FSize) and (FSelEnd.Digit < cDigitCount - 1) then
Inc(FSelEnd.Digit)
else
begin
FSelEnd.Digit := 0;
Inc(FSelEnd.Index);
end
end else
Inc(FSelEnd.Index);
end;
function TKCustomHexEditor.IsAddressPrefixStored: Boolean;
begin
Result := FAddressPrefix <> '0x';
end;
function TKCustomHexEditor.IsDrawStylesStored: Boolean;
begin
Result := FDrawStyles <> cDrawStylesDef;
end;
function TKCustomHexEditor.IsOptionsStored: Boolean;
begin
Result := FOptions <> [eoGroupUndo];
end;
procedure TKCustomHexEditor.KeyDown(var Key: Word; Shift: TShiftState);
var
I: Integer;
HK: TKEditKey;
begin
inherited;
Exclude(FStates, elIgnoreNextChar);
if not (csDesigning in ComponentState) then
begin
for I := 0 to Length(FKeyMapping) - 1 do
begin
HK := FKeyMapping[I].Key;
if (HK.Key = Key) and (HK.Shift = Shift) then
begin
ExecuteCommand(FKeyMapping[I].Command);
Key := 0;
Include(FStates, elIgnoreNextChar);
Exit;
end;
end;
if Key = VK_ESCAPE then
Include(FStates, elIgnoreNextChar);
end;
end;
procedure TKCustomHexEditor.KeyPress(var Key: Char);
var
I: Integer;
begin
inherited;
if not (csDesigning in ComponentState) then
begin
if not (elIgnoreNextChar in FStates) then
begin
case FEditArea of
eaDigits: I := DigitToBin(AnsiChar(Key));
eaText: I := Ord(Key);
else
I := -1;
end;
if I >= 0 then
ExecuteCommand(ecInsertChar, @I);
end else
Exclude(FStates, elIgnoreNextChar);
end;
end;
procedure TKCustomHexEditor.LoadFromFile(const FileName: TFileName);
var
Stream: TFileStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
procedure TKCustomHexEditor.LoadFromStream(Stream: TStream);
var
Size: Integer;
begin
Size := Stream.Size - Stream.Position;
if Size > 0 then
begin
Clear;
FSize := Size;
GetMem(FBuffer, FSize);
Stream.Read(FBuffer^, FSize);
BufferChanged;
end;
end;
procedure TKCustomHexEditor.MeasurePages(var Info: TKPrintMeasureInfo);
var
AD: TKHexEditorAreaDimensions;
PageLines, ActiveLines: Integer;
FitToPage, SelOnly: Boolean;
Scale: Double;
APageSetup: TKPrintPageSetup;
begin
APageSetup := PageSetup;
FitToPage := poFitToPage in APageSetup.Options;
SelOnly := APageSetup.Range = prSelectedOnly;
Scale := APageSetup.Scale / 100;
AD := GetAreaDimensions;
Info.OutlineWidth := AD.TotalHorz * FCharWidth;
if FitToPage then
Scale := APageSetup.PaintAreaWidth / Info.OutlineWidth;
PageLines := Round(APageSetup.PaintAreaHeight / Scale) div FCharHeight;
if SelOnly then
ActiveLines := DivUp(GetRealSelEnd.Index, FLineSize) - GetRealSelStart.Index div FLineSize
else
ActiveLines := LineCount;
Info.OutlineHeight := PageLines * FCharHeight;
Info.HorzPageCount := 1; // cut text off
Info.VertPageCount := DivUp(ActiveLines, PageLines);
Info.PageCount := Info.VertPageCount;
end;
procedure TKCustomHexEditor.ModifyScrollBar(ScrollBar, ScrollCode,
Delta: Integer; UpdateNeeded: Boolean);
var
I, J, K: Integer;
HasScrollBar: Boolean;
SI: TScrollInfo;
begin
HasScrollBar := (ScrollBar = SB_HORZ) and (ScrollBars = ssHorizontal) or
(ScrollBar = SB_VERT) and (ScrollBars = ssVertical) or (ScrollBars = ssBoth);
if HasScrollBar then
begin
FillChar(SI, SizeOf(TScrollInfo), 0);
SI.cbSize := SizeOf(TScrollInfo);
SI.fMask := SIF_PAGE or SIF_TRACKPOS;
GetScrollInfo(Handle, ScrollBar, SI);
{$IF DEFINED(LCLGTK2)}
{.$WARNING "scrollbar arrows still not working properly on GTK2 in some cases!"}
SI.nTrackPos := Delta;
{$IFEND}
end;
if ScrollBar = SB_HORZ then
begin
I := FLeftChar;
J := GetMaxLeftChar;
end else
begin
I := FTopLine;
J := GetMaxTopLine;
end;
K := I;
if ScrollCode = cScrollDelta then
Inc(I, Delta)
else if HasScrollBar then
case ScrollCode of
SB_LINEUP: Dec(I);
SB_LINEDOWN: Inc(I);
SB_PAGEUP: Dec(I, SI.nPage);
SB_PAGEDOWN: Inc(I, SI.nPage);
SB_THUMBTRACK, SB_THUMBPOSITION: I := SI.nTrackPos;
end;
I := MinMax(I, 0, J);
if K <> I then
begin
if HasScrollBar then
begin
FillChar(SI, SizeOf(TScrollInfo), 0);
SI.nPos := I;
SI.fMask := SIF_POS;
SetScrollInfo(Handle, ScrollBar, SI, True);
end;
if ScrollBar = SB_HORZ then
FLeftChar := I
else
FTopLine := I;
if UpdateNeeded then
begin
UpdateEditorCaret;
Invalidate;
end;
end;
end;
procedure TKCustomHexEditor.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
var
P: TPoint;
Command: TKEditCommand;
begin
inherited;
if Enabled and (Button = mbLeft) and not (ssDouble in Shift) then
begin
SafeSetFocus;
P := Point(X, Y);
if ssShift in Shift then
Command := ecSelGotoXY
else
Command := ecGotoXY;
if ExecuteCommand(Command, @P) then
Include(FStates, elMouseCapture);
end;
end;
procedure TKCustomHexEditor.MouseMove(Shift: TShiftState; X, Y: Integer);
var
P: TPoint;
R: TRect;
begin
inherited;
if (elMouseCapture in FStates) then
begin
P := Point(X, Y);
R := GetModifiedClientRect;
if PtInRect(R, P) then
UpdateSelEnd(P, False)
else if not FScrollTimer.Enabled then
ScrollTo(P, True, False);
end;
end;
procedure TKCustomHexEditor.MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
inherited;
Exclude(FStates, elMouseCapture);
end;
procedure TKCustomHexEditor.PaintLines(const Data: TKHexEditorPaintData);
var
HalfPosWidth, I, Index, J, K, L, M, MaxAddress, WHorz, WVert, WSep,
LeftIndent, VTextIndent: Integer;
BC1, BC2, FC1, FC2, PC1: TColor;
EditorFocused, DrawInactiveCaret, DrawNormal, DigitSep, SelCondition: Boolean;
S: AnsiString;
Fmt: string;
C: Char;
R, R1, RClip: TRect;
OldColorScheme: TKHexEditorColorScheme;
ASelStart, ASelEnd: TKHexEditorSelection;
AD: TKHexEditorAreaDimensions;
begin
{ this function must be reentrant because of print function
so there is necessary to backup all changes to global properties}
OldColorScheme := FColors.ColorScheme;
with Data.Canvas do
try
R := Data.PaintRect;
AD := GetAreaDimensions;
// add possible inter-character spacing (in Lazarus not fully implemented yet)
SetTextCharacterExtra(Handle, Data.CharSpacing);
LeftIndent := R.Left - Data.LeftChar * Data.CharWidth;
VTextIndent := (Data.CharHeight - Abs(Font.Height)) div 2;
HalfPosWidth := Data.CharWidth div 2;
Fmt := '';
MaxAddress := 0;
L := LineCount;
DrawInactiveCaret := not (Data.Printing or Data.CaretShown) and
(edInactiveCaret in FDrawStyles);
DrawNormal := not Data.Printing;
EditorFocused := HasFocus;
if FSelStart.Index <= FSelEnd.Index then
begin
ASelStart := FSelStart;
ASelEnd := FSelEnd;
end else
begin
ASelStart := FSelEnd;
ASelEnd := FSelStart;
end;
// preserve space for lines and separators
if edHorzLines in FDrawStyles then
WVert := Max(1, Data.CharHeight div 25)
else
WVert := 0;
if edVertLines in FDrawStyles then
WHorz := Max(1, Data.CharWidth div 20)
else
WHorz := 0;
if edSeparators in FDrawStyles then
WSep := Max(1, Data.CharWidth div 20)
else
WSep := 0;
// address area pre-comp
if edAddress in FDrawStyles then
begin
if FAddressMode = eamDec then
begin
C := 'd';
J := 10;
end else
begin
C := 'x';
J := 16;
end;
Fmt := Format('%s%%.%d%s', [FAddressPrefix, FAddressSize, C]);
MaxAddress := 1;
for I := 1 to FAddressSize do
MaxAddress := MaxAddress * J;
end;
// update color scheme
if Data.Printing then
begin
if Data.PaintColors then
FColors.ColorScheme := ecsNormal
else
FColors.ColorScheme := ecsGrayScale;
end else
begin
if Enabled or (FDisabledDrawStyle = eddNormal) then
FColors.ColorScheme := ecsNormal
else if FDisabledDrawStyle = eddGrayed then
FColors.ColorScheme := ecsGrayed
else
FColors.ColorScheme := ecsBright
end;
FColors.SingleBkGnd := edSingleBkGnd in FDrawStyles;
// get clip box for updating;
if Data.Printing then
RClip := R
else
GetClipBox(Handle, {$IFDEF FPC}@{$ENDIF}RClip);
// now paint text lines
for I := Data.TopLine to Min(L - 1, Data.BottomLine) do
begin
Brush.Style := bsSolid;
K := LeftIndent;
R.Bottom := R.Top + Data.CharHeight - WVert;
if (R.Top <= RClip.Bottom) and (R.Bottom >= RClip.Top) then
begin
if edAddress in FDrawStyles then
begin
Index := I * FLineSize;
Brush.Color := clRed;
if (DrawNormal or Data.PaintSelection) and ((ASelStart.Index <> ASelEnd.Index) or (ASelStart.Digit <> ASelEnd.Digit)) and
(Index + FLineSize - 1 >= ASelStart.Index) and (Index < ASelEnd.Index) then
begin
PC1 := FColors.LinesHighLight;
if (FEditArea = eaAddress) and (EditorFocused or Data.PaintSelection) then
begin
FC1 := FColors.SelTextFocused;
BC1 := FColors.SelBkGndFocused;
end else
begin
FC1 := FColors.SelText;
BC1 := FColors.SelBkGnd;
end;
end else
begin
PC1 := FColors.HorzLines;
FC1 := FColors.AddressText;
BC1 := FColors.AddressBkGnd;
end;
Brush.Color := BC1;
Font.Color := FC1;
R.Left := K;
Inc(K, AD.Address * Data.CharWidth);
R.Right := K;
J := I * FLineSize + FAddressOffset;
if MaxAddress <> 0 then J := J mod MaxAddress;
FillRect(R);
TextOut(R.Left, R.Top + VTextIndent, Format(Fmt, [J]));
if edHorzLines in FDrawStyles then
begin
Brush.Color := PC1;
FillRect(Rect(R.Left, R.Bottom, R.Right, R.Bottom + WVert));
end;
if AD.AddressOut > 0 then
begin
R.Left := K;
Inc(K, AD.AddressOut * Data.CharWidth);
R.Right := K;
Brush.Color := FColors.AddressBkGnd;
FillRect(Rect(R.Left, R.Top, R.Right - WSep, R.Bottom));
if edHorzLines in FDrawStyles then
begin
Brush.Color := FColors.HorzLines;
FillRect(Rect(R.Left, R.Bottom, R.Right - WSep, R.Bottom + WVert));
end;
end;
end;
if edDigits in FDrawStyles then
begin
if AD.DigitsIn > 0 then
begin
R.Left := K;
Inc(K, AD.DigitsIn * Data.CharWidth);
R.Right := K;
Brush.Color := FColors.DigitBkGnd;
FillRect(Rect(R.Left + WSep, R.Top, R.Right, R.Bottom));
if edHorzLines in FDrawStyles then
begin
Brush.Color := FColors.HorzLines;
FillRect(Rect(R.Left + WSep, R.Bottom, R.Right, R.Bottom + WVert));
end;
end;
Index := 0;
for J := 0 to FLineSize - 1 do
begin
Index := I * FLineSize + J;
DigitSep := (J < FLineSize - 1) and ((J + 1) mod FDigitGrouping = 0);
R.Left := K;
Inc(K, cDigitCount * Data.CharWidth);
R.Right := K;
if Index <= FSize then
begin
if Index < FSize then
S := AnsiString(Format(cFmtText, [FBuffer[Index]]))
else
S := ' ';
if (Index <> FSelStart.Index) and (Index <> FSelEnd.Index) then
begin
SelCondition := (Index >= ASelStart.Index) and (Index < ASelEnd.Index);
if (DrawNormal or Data.PaintSelection) and SelCondition then
begin
PC1 := FColors.LinesHighLight;
if (FEditArea = eaDigits) and (EditorFocused or Data.PaintSelection) then
begin
FC1 := FColors.SelTextFocused;
BC1 := FColors.SelBkGndFocused;
end else
begin
FC1 := FColors.SelText;
BC1 := FColors.SelBkGnd;
end;
FC2 := FColors.InactiveCaretSelText;
BC2 := FColors.InactiveCaretSelBkGnd;
end else
begin
PC1 := FColors.HorzLines;
if DrawNormal or Data.PaintAll or SelCondition then
begin
if (J div FDigitGrouping) and 1 = 0 then
FC1 := FColors.DigitTextEven
else
FC1 := FColors.DigitTextOdd;
end else
FC1 := FColors.DigitBkGnd;
BC1 := FColors.DigitBkGnd;
FC2 := FColors.InactiveCaretText;
BC2 := FColors.InactiveCaretBkGnd;
end;
Brush.Color := BC1;
Font.Color := FC1;
Brush.Style := bsSolid;
FillRect(R);
Brush.Style := bsClear;
TextOut(R.Left, R.Top + VTextIndent, Char(S[1]));
TextOut(R.Left + Data.CharWidth, R.Top + VTextIndent, Char(S[2]));
if (Index = FSelEnd.Index) and DrawInactiveCaret then
begin
// draw inactive caret - place into previous drawn text
R1 := R;
Inc(R1.Left, Data.CharWidth * Min(FSelEnd.Digit, cDigitCount - 1));
R1.Right := R1.Left + Data.CharWidth;
Font.Color := FC2;
Brush.Color := BC2;
Brush.Style := bsSolid;
FillRect(R1);
Brush.Style := bsClear;
TextOut(R1.Left, R1.Top + VTextIndent, string(S));
end;
if edHorzLines in FDrawStyles then
begin
Brush.Color := PC1;
Brush.Style := bsSolid;
FillRect(Rect(R.Left, R.Bottom, R.Right, R.Bottom + WVert));
end;
end else
begin
R1 := R;
R1.Right := R1.Left;
Inc(R1.Right, Data.CharWidth);
for M := 0 to cDigitCount - 1 do
begin
SelCondition :=
(ASelStart.Index = ASelEnd.Index) and (
(M >= ASelStart.Digit) and (M < ASelEnd.Digit) or
(M >= ASelEnd.Digit) and (M < ASelStart.Digit)
)
or
(ASelStart.Index <> ASelEnd.Index) and (
(Index = ASelStart.Index) and (M >= ASelStart.Digit) or
(Index = ASelEnd.Index) and (M < ASelEnd.Digit)
);
if (DrawNormal or Data.PaintSelection) and SelCondition then
begin
PC1 := FColors.LinesHighLight;
if DrawInactiveCaret and (Index = FSelEnd.Index) and (M = FSelEnd.Digit) then
begin
FC1 := FColors.InactiveCaretSelText;
BC1 := FColors.InactiveCaretSelBkGnd;
end
else if (FEditArea = eaDigits) and (EditorFocused or Data.PaintSelection) then
begin
FC1 := FColors.SelTextFocused;
BC1 := FColors.SelBkGndFocused;
end else
begin
FC1 := FColors.SelText;
BC1 := FColors.SelBkGnd;
end;
end else
begin
PC1 := FColors.HorzLines;
if DrawInactiveCaret and (Index = FSelEnd.Index) and (M = FSelEnd.Digit) then
begin
FC1 := FColors.InactiveCaretText;
BC1 := FColors.InactiveCaretBkGnd;
end else
begin
if DrawNormal or Data.PaintAll or SelCondition then
begin
if (J div FDigitGrouping) and 1 = 0 then
FC1 := FColors.DigitTextEven
else
FC1 := FColors.DigitTextOdd;
end else
FC1 := FColors.DigitBkGnd;
BC1 := FColors.DigitBkGnd;
end;
end;
Brush.Color := BC1;
Font.Color := FC1;
Brush.Style := bsSolid;
FillRect(R1);
Brush.Style := bsClear;
TextOut(R1.Left, R1.Top + VTextIndent, Char(S[M + 1]));
if edHorzLines in FDrawStyles then
begin
Brush.Color := PC1;
Brush.Style := bsSolid;
FillRect(Rect(R1.Left, R1.Bottom, R1.Right, R1.Bottom + WVert));
end;
R1.Left := R1.Right;
Inc(R1.Right, Data.CharWidth);
end;
end;
if DigitSep then
begin
if Index < FSize then
M := Data.CharWidth
else
M := HalfPosWidth;
Brush.Color := FColors.DigitBkGnd;
Brush.Style := bsSolid;
FillRect(Rect(R.Right, R.Top, R.Right + Data.CharWidth, R.Bottom));
if edHorzLines in FDrawStyles then
begin
Brush.Color := FColors.HorzLines;
FillRect(Rect(R.Right, R.Bottom, R.Right + M, R.Bottom + WVert));
end;
if edVertLines in FDrawStyles then
begin
M := R.Right + HalfPosWidth;
Brush.Color := FColors.VertLines;
FillRect(Rect(M, R.Top, M + WHorz, R.Bottom));
end;
Inc(K, Data.CharWidth);
end;
end else
begin
Inc(K, Integer(DigitSep) * Data.CharWidth);
Brush.Color := FColors.DigitBkGnd;
Brush.Style := bsSolid;
FillRect(Rect(R.Left, R.Top, K, R.Bottom + WVert));
end;
end;
if AD.DigitsOut > 0 then
begin
R.Left := K;
Inc(K, AD.DigitsOut * Data.CharWidth);
R.Right := K;
Brush.Style := bsSolid;
Brush.Color := FColors.DigitBkGnd;
FillRect(Rect(R.Left, R.Top, R.Right - WSep, R.Bottom));
if edHorzLines in FDrawStyles then
begin
if Index < FSize then
Brush.Color := FColors.HorzLines
else
Brush.Color := FColors.DigitBkGnd;
FillRect(Rect(R.Left, R.Bottom, R.Right - WSep, R.Bottom + WVert));
end;
end;
end;
if edText in FDrawStyles then
begin
if AD.TextIn > 0 then
begin
R.Left := K;
Inc(K, AD.TextIn * Data.CharWidth);
R.Right := K;
Brush.Color := FColors.TextBkGnd;
Brush.Style := bsSolid;
FillRect(Rect(R.Left + WSep, R.Top, R.Right, R.Bottom));
if edHorzLines in FDrawStyles then
begin
Brush.Color := FColors.HorzLines;
FillRect(Rect(R.Left + WSep, R.Bottom, R.Right, R.Bottom + WVert));
end;
end;
for J := 0 to FLineSize - 1 do
begin
Index := I * FLineSize + J;
R.Left := K;
Inc(K, Data.CharWidth);
R.Right := K;
if Index <= FSize then
begin
SelCondition := (Index >= ASelStart.Index) and (Index < ASelEnd.Index);
if (DrawNormal or Data.PaintSelection) and SelCondition then
begin
PC1 := FColors.LinesHighLight;
if DrawInactiveCaret and (Index = FSelEnd.Index) then
begin
FC1 := FColors.InactiveCaretSelText;
BC1 := FColors.InactiveCaretSelBkGnd;
end
else if (FEditArea = eaText) and (EditorFocused or Data.PaintSelection) then
begin
FC1 := FColors.SelTextFocused;
BC1 := FColors.SelBkGndFocused;
end else
begin
FC1 := FColors.SelText;
BC1 := FColors.SelBkGnd;
end;
end else
begin
PC1 := FColors.HorzLines;
if DrawInactiveCaret and (Index = FSelEnd.Index) then
begin
FC1 := FColors.InactiveCaretText;
BC1 := FColors.InactiveCaretBkGnd;
end else
begin
if DrawNormal or Data.PaintAll or SelCondition then
FC1 := FColors.TextText
else
FC1 := FColors.TextBkgnd;
BC1 := FColors.TextBkgnd;
end;
end;
Brush.Color := BC1;
Brush.Style := bsSolid;
FillRect(R);
Brush.Style := bsClear;
if Index < FSize then
begin
Font.Color := FC1;
TextOut(R.Left, R.Top + VTextIndent, Char(FCharMapping[FBuffer[Index]]));
end;
if edHorzLines in FDrawStyles then
begin
Brush.Color := PC1;
Brush.Style := bsSolid;
FillRect(Rect(R.Left, R.Bottom, R.Right, R.Bottom + WVert));
end;
end else
begin
Brush.Color := FColors.TextBkGnd;
Brush.Style := bsSolid;
FillRect(Rect(R.Left, R.Top, K, R.Bottom + WVert));
end;
end;
end;
end;
Inc(R.Top, Data.CharHeight);
end;
// now complete blank areas below text and optionally paint separators
K := LeftIndent;
R.Bottom := Data.PaintRect.Bottom;
Brush.Style := bsSolid;
if edAddress in FDrawStyles then
begin
R.Left := K;
Inc(K, (AD.Address + AD.AddressOut) * Data.CharWidth);
R.Right := K; if FDrawStyles * [edDigits, edText] <> [] then Dec(R.Right, WSep);
if R.Top < R.Bottom then
begin
Brush.Color := FColors.AddressBkGnd;
FillRect(R);
end;
if (edSeparators in FDrawStyles) and (FDrawStyles * [edDigits, edText] <> []) then
begin
Brush.Color := FColors.Separators;
FillRect(Rect(K - WSep, Data.PaintRect.Top, K + WSep, Data.PaintRect.Bottom));
end;
end;
if edDigits in FDrawStyles then
begin
R.Left := K; if edAddress in FDrawStyles then Inc(R.Left, WSep);
Inc(K, (AD.Digits + AD.DigitsIn + AD.DigitsOut) * Data.CharWidth);
R.Right := K; if edText in FDrawStyles then Dec(R.Right, WSep);
if R.Top < R.Bottom then
begin
Brush.Color := FColors.DigitBkGnd;
FillRect(R);
end;
if (edSeparators in FDrawStyles) and (edText in FDrawStyles) then
begin
Brush.Color := FColors.Separators;
FillRect(Rect(K - WSep, Data.PaintRect.Top, K + WSep, Data.PaintRect.Bottom));
end;
end;
if edText in FDrawStyles then
begin
R.Left := K; if FDrawStyles * [edAddress, edDigits] <> [] then Inc(R.Left, WSep);
Inc(K, (AD.TextIn + AD.Text) * Data.CharWidth);
R.Right := K;
if R.Top < R.Bottom then
begin
Brush.Color := FColors.TextBkGnd;
FillRect(R);
end;
end;
if K < ClientWidth then
begin
Brush.Color := FColors.BkGnd;
FillRect(Rect(K, 0, ClientWidth, ClientHeight));
end;
finally
FColors.ColorScheme := OldColorScheme;
end;
end;
procedure TKCustomHexEditor.PaintPage;
var
ActiveLines, AreaWidth, AreaHeight, FirstLine, PageLines: Integer;
SelOnly: Boolean;
TmpRect, TmpRect1: TRect;
APageSetup: TKPrintPageSetup;
Data: TKHexEditorPaintData;
begin
APageSetup := PageSetup;
SelOnly := APageSetup.Range = prSelectedOnly;
AreaWidth := Round(APageSetup.PaintAreaWidth / APageSetup.CurrentScale);
AreaHeight := Round(APageSetup.PaintAreaHeight / APageSetup.CurrentScale);
PageLines := AreaHeight div FCharHeight;
if SelOnly then
begin
FirstLine := GetRealSelStart.Index div FLineSize;
ActiveLines := DivUp(GetRealSelEnd.Index, FLineSize) - FirstLine;
end else
begin
FirstLine := 0;
ActiveLines := LineCount;
end;
TmpRect := Rect(0, 0, APageSetup.OutlineWidth, APageSetup.OutlineHeight);
TmpRect1 := Rect(0, 0, AreaWidth, AreaHeight);
IntersectRect(TmpRect, TmpRect, TmpRect1);
TmpRect1 := TmpRect;
TranslateRectToDevice(APageSetup.Canvas.Handle, TmpRect1);
SelectClipRect(APageSetup.Canvas.Handle, TmpRect1);
Data.Canvas := APageSetup.Canvas;
Data.Canvas.Font := Font;
Data.Canvas.Font.Height := Abs(Font.Height);
Data.PaintRect := TmpRect;
Data.TopLine := (APageSetup.CurrentPage - 1) * PageLines;
Data.BottomLine := Min(Data.TopLine + PageLines, ActiveLines) - 1;
Inc(Data.TopLine, FirstLine);
Inc(Data.BottomLine, FirstLine);
Data.LeftChar := 0;
Data.CharWidth := FCharWidth;
Data.CharHeight := FCharHeight;
Data.CharSpacing := FTotalCharSpacing;
Data.Printing := True;
Data.PaintSelection := poPaintSelection in APageSetup.Options;
Data.PaintAll := not SelOnly;
Data.PaintColors := poUseColor in APageSetup.Options;
PaintLines(Data);
end;
procedure TKCustomHexEditor.PaintToCanvas(ACanvas: TCanvas);
var
Data: TKHexEditorPaintData;
begin
ACanvas.Font := Font;
with Data do
begin
Canvas := ACanvas;
PaintRect := ClientRect;
LeftChar := FLeftChar;
TopLine := FTopLine;
CharWidth := FCharWidth;
CharHeight := FCharHeight;
BottomLine := TopLine + ClientHeight div FCharHeight;
CharSpacing := FTotalCharSpacing;
Printing := False;
PaintSelection := False;
CaretShown := elCaretVisible in FStates;
end;
{$IFDEF FPC}
if Data.CaretShown then
HideEditorCaret;
try
{$ENDIF}
PaintLines(Data);
{$IFDEF FPC}
finally
if Data.CaretShown then
ShowEditorCaret;
end;
{$ENDIF}
end;
procedure TKCustomHexEditor.PaintToCanvasEx(ACanvas: TCanvas; ARect: TRect; ALeftChar, ATopLine: Integer);
var
Data: TKHexEditorPaintData;
Region: HRGN;
begin
ACanvas.Font := Font;
with Data do
begin
Canvas := ACanvas;
PaintRect := ARect;
LeftChar := ALeftChar;
TopLine := ATopLine;
CharWidth := FCharWidth;
CharHeight := FCharHeight;
BottomLine := TopLine + (ARect.Bottom - ARect.Top) div FCharHeight;
CharSpacing := FTotalCharSpacing;
Printing := False;
PaintSelection := False;
end;
Region := CreateRectRgnIndirect(ARect);
try
SelectClipRgn(ACanvas.Handle, Region);
try
PaintLines(Data);
finally
SelectClipRgn(ACanvas.Handle, 0);
end;
finally
DeleteObject(Region);
end;
end;
function TKCustomHexEditor.PointToSel(P: TPoint; OutOfArea: Boolean; var Area: TKHexEditorArea): TKHexEditorSelection;
var
Digit, HalfPosWidth, I, X, X1, XMax: Integer;
DigitSep: Boolean;
AD: TKHexEditorAreaDimensions;
Sel: TKHexEditorSelection;
begin
Result := MakeSelection(cInvalidIndex, 0);
P.X := P.X + FLeftChar * FCharWidth;
P.Y := P.Y div FCharHeight + FTopLine;
AD := GetAreaDimensions;
HalfPosWidth := FCharWidth div 2;
X := 0;
if OutOfArea then
P.Y := MinMax(P.Y, 0, LineCount - 1)
else
Area := eaNone;
if P.Y < LineCount then
begin
if edAddress in FDrawStyles then
begin
XMax := X + (AD.Address + AD.AddressOut) * FCharWidth;
if not OutOfArea or (Area = eaAddress) then
if (P.X >= X) and (P.X < XMax) then
begin
Result := MakeSelection(P.Y * FLineSize, 0);
Area := eaAddress;
end
else if Area = eaAddress then // OutOfArea = True
begin
Result.Index := P.Y * FLineSize;
if P.X >= XMax then
Inc(Result.Index, FLineSize);
end;
X := XMax;
end;
if (P.X >= X) or OutOfArea then
begin
if edDigits in FDrawStyles then
begin
XMax := X + (AD.Digits + AD.DigitsIn + AD.DigitsOut) * FCharWidth;
if not OutOfArea or (Area = eaDigits) then
if (P.X >= X) and (P.X < XMax) then
begin
Inc(X, AD.DigitsIn * FCharWidth);
for I := 0 to FLineSize - 1 do
begin
DigitSep := (I < FLineSize - 1) and ((I + 1) mod FDigitGrouping = 0);
X1 := X;
Inc(X, cDigitCount * FCharWidth);
if DigitSep then
Inc(X, HalfPosWidth)
else if I = FLineSize - 1 then
Inc(X, AD.DigitsOut * FCharWidth);
if P.X < X then
begin
Digit := (Max(P.X - X1, 0) + HalfPosWidth) div FCharWidth;
Sel := MakeSelection(P.Y * FLineSize + I, Digit);
if (Digit >= cDigitCount) and (Sel.Index < FSize) then // don't split the FSize character box
begin
Inc(Sel.Index);
Sel.Digit := 0;
end;
if (Sel.Index <= FSize) or OutOfArea then
begin
Result := Sel;
Area := eaDigits;
end;
Break;
end;
if DigitSep then
Inc(X, HalfPosWidth);
end;
end
else if Area = eaDigits then // OutOfArea = True
begin
Result.Index := P.Y * FLineSize;
if P.X >= XMax then
Inc(Result.Index, FLineSize);
end;
X := XMax;
end;
if ((P.X >= X) or OutOfArea) and (edText in FDrawStyles) then
begin
XMax := X + (AD.Text + AD.TextIn) * FCharWidth;
if not OutOfArea or (Area = eaText) then
if (P.X >= X) and (P.X < XMax) then
begin
Inc(X, AD.TextIn * FCharWidth);
Sel := MakeSelection(P.Y * FLineSize, 0);
I := Max(P.X - X, 0) div FCharWidth;
if Sel.Index + I = FSize then
Sel.Index := FSize // don't split the FSize character box
else
Inc(Sel.Index, (Max(P.X - X, 0) + HalfPosWidth) div FCharWidth);
if (Sel.Index <= FSize) or OutOfArea then
begin
Result := Sel;
Area := eaText;
end;
end
else if Area = eaText then // OutOfArea = True
begin
Result.Index := P.Y * FLineSize;
if P.X >= XMax then
Inc(Result.Index, FLineSize);
end;
end;
end;
end;
ValidateSelection(Result, Area);
end;
procedure TKCustomHexEditor.SafeSetFocus;
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
if (Form <> nil) and Form.Visible and Form.Enabled and not (csDestroying in Form.ComponentState)
and Visible and Enabled then
Form.ActiveControl := Self;
end;
procedure TKCustomHexEditor.SaveToFile(const FileName: TFileName);
var
Stream: TFileStream;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try
SaveToStream(Stream);
finally
Stream.Free;
end;
end;
procedure TKCustomHexEditor.SaveToStream(Stream: TStream);
begin
if FBuffer <> nil then
Stream.Write(FBuffer^, FSize);
end;
procedure TKCustomHexEditor.ScrollBy(HChars, VChars: Integer; UpdateNeeded: Boolean);
begin
if HChars <> 0 then
ModifyScrollBar(SB_HORZ, cScrollDelta, HChars, UpdateNeeded);
if VChars <> 0 then
ModifyScrollBar(SB_VERT, cScrollDelta, VChars, UpdateNeeded);
end;
procedure TKCustomHexEditor.ScrollTo(Point: TPoint; Timed, AlwaysScroll: Boolean);
var
ScrollHorz: Boolean;
R: TRect;
begin
// disable horizontal overscroll when scrolling e.g. with mouse
ScrollHorz := AlwaysScroll or (FSelEnd.Index mod FLineSize <> 0) and
(FSelEnd.Index < FSize) or (FSelEnd.Digit > 0);
R := GetModifiedClientRect;
if ScrollHorz then
begin
if Point.X < R.Left then
FScrollDeltaX := DivDown(Point.X, FCharWidth)
else if Point.X >= R.Right then
FScrollDeltaX := (Point.X - R.Right) div FCharWidth + 1
else
FScrollDeltaX := 0;
end else
FScrollDeltaX := 0;
if Point.Y < R.Top then
FScrollDeltaY := DivDown(Point.Y, FCharHeight)
else if Point.Y >= R.Bottom then
FScrollDeltaY := (Point.Y - R.Bottom) div FCharHeight + 1
else
FScrollDeltaY := 0;
if (FScrollDeltaX <> 0) or (FScrollDeltaY <> 0) then
if Timed then
begin
ScrollBy(FScrollDeltaX, FScrollDeltaY, False);
FScrollTimer.Enabled := True;
end else
ScrollBy(FScrollDeltaX, FScrollDeltaY, True);
UpdateSelEnd(Point, True);
end;
procedure TKCustomHexEditor.ScrollTimerHandler(Sender: TObject);
var
P: TPoint;
begin
GetCursorPos(P);
P := ScreenToClient(P);
if (elMouseCapture in FStates) and not (Dragging or
PtInRect(GetModifiedClientRect, P)) then
ScrollTo(P, True, False)
else
FScrollTimer.Enabled := False;
end;
function TKCustomHexEditor.SelAvail: Boolean;
begin
Result := SelLength.Index > 0;
end;
procedure TKCustomHexEditor.SelectionChanged(StartEqualEnd: Boolean; ScrollToView: Boolean = True);
begin
ValidateSelection(FSelEnd, FEditArea);
if StartEqualEnd then
FSelStart := FSelEnd
else
ValidateSelection(FSelStart, FEditArea);
if HasParent then
begin
if ScrollToView and (FEditArea <> eaNone) then
ScrollTo(SelToPoint(FSelEnd, FEditArea), False, True);
UpdateEditorCaret;
Invalidate;
InvalidatePageSetup;
end;
end;
function TKCustomHexEditor.SelectionValid(Value: TKHexEditorSelection; Area: TKHexEditorArea): Boolean;
begin
Result := (Area <> eaNone) and (
(Value.Index >= 0) and (Value.Index < FSize) or
(Value.Index = FSize) and (Value.Digit = 0))
end;
function TKCustomHexEditor.SelToPoint(Value: TKHexEditorSelection; Area: TKHexEditorArea): TPoint;
var
AD: TKHexEditorAreaDimensions;
begin
Result := Point(0, 0);
AD := GetAreaDimensions;
ValidateSelection(Value, Area);
if (Area = eaDigits) and (edDigits in FDrawStyles) then
begin
Result.X := ((Value.Index mod FLineSize) div FDigitGrouping * (cDigitCount * FDigitGrouping + 1) +
(Value.Index mod FLineSize) mod FDigitGrouping * cDigitCount + Value.Digit + AD.DigitsIn)
end else if (Area = eaText) and (edText in FDrawStyles) then
Result.X := (Value.Index mod FLineSize + AD.DigitsIn + AD.Digits + AD.DigitsOut + AD.TextIn)
else if Area = eaAddress then
begin
if edDigits in FDrawStyles then
Result.X := AD.DigitsIn
else if edText in FDrawStyles then
Result.X := AD.TextIn;
end;
Result.X := (Result.X + AD.Address + AD.AddressOut - FLeftChar) * FCharWidth;
Result.Y := (Value.Index div FLineSize - FTopLine) * FCharHeight;
end;
procedure TKCustomHexEditor.SetAddressCursor(Value: TCursor);
begin
if Value <> FAddressCursor then
begin
FAddressCursor := Value;
UpdateMouseCursor;
end;
end;
procedure TKCustomHexEditor.SetAddressMode(Value: TKHexEditorAddressMode);
begin
if Value <> FAddressMode then
begin
FAddressMode := Value;
Invalidate;
end;
end;
procedure TKCustomHexEditor.SetAddressOffset(Value: Integer);
begin
if Value <> FAddressOffset then
begin
FAddressOffset := Value;
Invalidate;
end;
end;
procedure TKCustomHexEditor.SetAddressPrefix(const Value: string);
begin
if Value <> FAddressPrefix then
begin
FAddressPrefix := Value;
UpdateScrollRange;
end;
end;
procedure TKCustomHexEditor.SetAddressSize(Value: Integer);
begin
Value := MinMax(Value, cAddressSizeMin, cAddressSizeMax);
if Value <> FAddressSize then
begin
FAddressSize := Value;
UpdateScrollRange;
end;
end;
procedure TKCustomHexEditor.SetAreaSpacing(Value: Integer);
begin
Value := MinMax(Value, cAreaSpacingMin, cAreaSpacingMax);
if Value <> FAreaSpacing then
begin
FAreaSpacing := Value;
UpdateScrollRange;
end;
end;
procedure TKCustomHexEditor.SetCharMapping(const Value: TKEditCharMapping);
begin
if not CompareMem(@Value, @FCharMapping, SizeOf(TKEditCharMapping)) and
(edText in FDrawStyles) then
Invalidate;
end;
procedure TKCustomHexEditor.SetCharSpacing(Value: Integer);
begin
Value := MinMax(Value, cCharSpacingMin, cCharSpacingMax);
if Value <> FCharSpacing then
begin
FCharSpacing := Value;
UpdateCharMetrics;
UpdateScrollRange;
end;
end;
procedure TKCustomHexEditor.SetColors(Value: TKHexEditorColors);
begin
FColors.Assign(Value);
end;
procedure TKCustomHexEditor.SetCommandKey(Index: TKEditCommand; Value: TKEditKey);
var
I: Integer;
begin
for I := 0 to Length(FKeyMapping) - 1 do
if FKeyMapping[I].Command = Index then
begin
FKeyMapping[I].Key := Value;
Exit;
end;
end;
procedure TKCustomHexEditor.SetData(Value: TDataSize);
begin
if (Value.Data <> FBuffer) or (Value.Size <> FSize) then
begin
Clear;
if Value.Data <> nil then
begin
FSize := Value.Size;
GetMem(FBuffer, FSize);
System.Move(Value.Data^, FBuffer^, FSize);
BufferChanged;
end;
end;
end;
procedure TKCustomHexEditor.SetDigitGrouping(Value: Integer);
begin
Value := MinMax(Value, cDigitGroupingMin, Min(FLineSize, cDigitGroupingMax));
if Value <> FDigitGrouping then
begin
FDigitGrouping := Value;
UpdateScrollRange;
end;
end;
procedure TKCustomHexEditor.SetDisabledDrawStyle(Value: TKHexEditorDisabledDrawStyle);
begin
if Value <> FDisabledDrawStyle then
begin
FDisabledDrawStyle := Value;
if not Enabled then
Invalidate;
end;
end;
procedure TKCustomHexEditor.SetDrawStyles(const Value: TKHexEditorDrawStyles);
begin
if Value <> FDrawStyles then
begin
FDrawStyles := Value;
EditAreaChanged; // must be called first
UpdateScrollRange;
end;
end;
procedure TKCustomHexEditor.SetEditArea(Value: TKHexEditorArea);
begin
if Value <> FEditArea then
begin
FEditArea := Value;
EditAreaChanged;
if Value <> FEditArea then
Invalidate;
end;
end;
procedure TKCustomHexEditor.SetKeyMapping(const Value: TKEditKeyMapping);
begin
SetLength(FKeyMapping, Length(Value));
Move(Value, FKeyMapping, Length(Value) * SizeOf(TKEditCommandAssignment));
end;
procedure TKCustomHexEditor.SetLineHeightPercent(Value: Integer);
begin
Value := MinMax(Value, cLineHeightPercentMin, cLineHeightPercentMax);
if Value <> FLineHeightPercent then
begin
FLineHeightPercent := Value;
UpdateCharMetrics;
UpdateScrollRange;
end;
end;
procedure TKCustomHexEditor.SetLeftChar(Value: Integer);
begin
Value := MinMax(Value, 0, GetMaxLeftChar);
if Value <> FLeftChar then
ScrollBy(Value - FLeftChar, 0, True);
end;
procedure TKCustomHexEditor.SetLines(Index: Integer; const Value: TDataSize);
var
I, Size: Integer;
begin
I := Index * FLineSize;
if (Value.Data <> nil) and (Value.Size > 0) and (I >= 0) and (I <= FSize) then
begin
Size := Min(FLineSize, Value.Size);
if I + Size > FSize then
begin
FSize := Size;
ReallocMem(FBuffer, FSize);
end;
System.Move(Value.Data^, FBuffer[I], Size);
BufferChanged;
end;
end;
procedure TKCustomHexEditor.SetLineSize(Value: Integer);
begin
Value := MinMax(Value, cLineSizeMin, cLineSizeMax);
if Value <> FLineSize then
begin
FLineSize := Value;
UpdateScrollRange;
end;
end;
procedure TKCustomHexEditor.SetModified(Value: Boolean);
begin
if Value <> GetModified then
begin
if Value then
Include(FStates, elModified)
else
begin
Exclude(FStates, elModified);
if eoUndoAfterSave in FOptions then
FUndoList.Modified := False
else
begin
FUndoList.Clear;
FRedoList.Clear;
end;
end;
end;
end;
function TKCustomHexEditor.SetMouseCursor(X, Y: Integer): Boolean;
var
ACursor: TCursor;
P: TPoint;
Area: TKHexEditorArea;
begin
P := Point(X, Y);
PointToSel(P, False, Area);
if PtInRect(ClientRect, P) then
begin
case Area of
eaAddress: ACursor := FAddressCursor;
eaDigits: ACursor := crIBeam;
eaText: ACursor := crIBeam;
else
ACursor := crDefault;
end;
end else
ACursor := crDefault;
{$IFDEF FPC}
FCursor := ACursor;
SetTempCursor(ACursor);
{$ELSE}
Windows.SetCursor(Screen.Cursors[ACursor]);
{$ENDIF}
Result := True;
end;
procedure TKCustomHexEditor.SetOptions(const Value: TKEditOptions);
{$IFDEF USE_WINAPI}
var
UpdateDropFiles: Boolean;
{$ENDIF}
begin
if Value <> FOptions then
begin
{$IFDEF USE_WINAPI}
UpdateDropFiles := (eoDropFiles in Value) <> (eoDropFiles in FOptions);
FOptions := Value;
// (un)register HWND as drop target
if UpdateDropFiles and not (csDesigning in ComponentState) and HandleAllocated then
DragAcceptFiles(Handle, (eoDropFiles in fOptions));
{$ELSE}
FOptions := Value;
{$ENDIF}
end;
end;
procedure TKCustomHexEditor.SetReadOnly(Value: Boolean);
begin
if Value <> GetReadOnly then
begin
if Value then
Include(FStates, elReadOnly)
else
Exclude(FStates, elReadOnly);
end;
end;
procedure TKCustomHexEditor.SetScrollBars(Value: TScrollStyle);
begin
if Value <> FScrollBars then
begin
FScrollBars := Value;
{$IFDEF FPC}
UpdateSize;
{$ELSE}
RecreateWnd;
{$ENDIF}
end;
end;
procedure TKCustomHexEditor.SetScrollSpeed(Value: Cardinal);
begin
Value := MinMax(Integer(Value), cScrollSpeedMin, cScrollSpeedMax);
if Value <> FScrollSpeed then
begin
FScrollSpeed := Value;
FScrollTimer.Enabled := False;
FScrollTimer.Interval := FScrollSpeed;
end;
end;
procedure TKCustomHexEditor.SetSelEnd(Value: TKHexEditorSelection);
begin
if (Value.Index <> FSelEnd.Index) or (Value.Digit <> FSelEnd.Digit) then
begin
FSelEnd := Value;
SelectionChanged(False, False);
Invalidate;
end;
end;
procedure TKCustomHexEditor.SetSelLength(Value: TKHexEditorSelection);
var
X: TKHexEditorSelection;
begin
X := GetSelLength;
if (Value.Index <> X.Index) or (Value.Digit <> X.Digit) then
begin
FSelEnd.Index := FSelStart.Index + Value.Index;
FSelEnd.Digit := FSelStart.Digit + Value.Digit;
if FSelEnd.Digit >= cDigitCount then
Inc(FSelEnd.Index);
SelectionChanged(False, False);
Invalidate;
end;
end;
procedure TKCustomHexEditor.SetSelStart(Value: TKHexEditorSelection);
begin
if (Value.Index <> FSelStart.Index) or (Value.Digit <> FSelStart.Digit) then
begin
FSelStart := Value;
SelectionChanged(False, False);
Invalidate;
end;
end;
procedure TKCustomHexEditor.SetTopLine(Value: Integer);
begin
Value := MinMax(Value, 0, GetMaxTopLine);
if Value <> FTopLine then
ScrollBy(0, Value - FTopLine, True);
end;
procedure TKCustomHexEditor.SetUndoLimit(Value: Integer);
begin
Value := MinMax(Value, cUndoLimitMin, cUndoLimitMax);
if Value <> FUndoList.Limit then
begin
FUndoList.Limit := Value;
FRedoList.Limit := Value;
end;
end;
procedure TKCustomHexEditor.HideEditorCaret;
var
P: TPoint;
begin
P := SelToPoint(FSelEnd, FEditArea);
HideCaret(Handle);
{$IFDEF FPC}SetCaretPosEx(Handle,{$ELSE}SetCaretPos({$ENDIF} P.X, P.Y + 1);
end;
procedure TKCustomHexEditor.ShowEditorCaret;
var
P: TPoint;
begin
P := SelToPoint(FSelEnd, FEditArea);
{$IFDEF FPC}SetCaretPosEx(Handle,{$ELSE}SetCaretPos({$ENDIF} P.X, P.Y + 1);
ShowCaret(Handle);
end;
procedure TKCustomHexEditor.UndoChange(Sender: TObject; ItemReason: TKHexEditorChangeReason);
begin
if (Sender = FUndoList) and (ItemReason <> crCaretPos) then
DoChange;
end;
procedure TKCustomHexEditor.UpdateEditorCaret(Recreate: Boolean = False);
var
CW, CH: Integer;
begin
Include(FStates, elCaretUpdate);
try
if Enabled and Focused and (FEditArea in [eaDigits, eaText]) and not (csDesigning in ComponentState) then
begin
if not (elCaretVisible in FStates) or Recreate then
begin
if elOverwrite in FStates then
CW := FCharWidth
else
CW := Max(2, (Abs(Font.Height) * 2) div 25);
if edHorzLines in FDrawStyles then
CH := FCharHeight - Max(1, FCharHeight div 25)
else
CH := FCharHeight;
{$IFDEF FPC}
CreateCaret(Handle, 0, CW, CH - 2);
{$ELSE}
if CreateCaret(Handle, 0, CW, CH - 2) then
{$ENDIF}
Include(FStates, elCaretVisible);
Invalidate;
end;
if elCaretVisible in FStates then
ShowEditorCaret;
end else
begin
Exclude(FStates, elCaretVisible);
HideEditorCaret;
{$IFDEF FPC}
DestroyCaret(Handle);
{$ELSE}
DestroyCaret;
{$ENDIF}
end;
finally
Exclude(FStates, elCaretUpdate);
end;
end;
procedure TKCustomHexEditor.UpdateCharMetrics;
var
DC: HDC;
TM: TTextMetric;
begin
DC := GetDC(0);
try
SelectObject(DC, Font.Handle);
GetTextMetrics(DC, TM);
FTotalCharSpacing := FCharSpacing * 2;
// ensure even char spacing because of PointToSel
if TM.tmAveCharWidth and 1 <> 0 then
Inc(FTotalCharSpacing);
FCharWidth := TM.tmAveCharWidth + FTotalCharSpacing;
FCharHeight := TM.tmHeight * FLineHeightPercent div 100;
finally
ReleaseDC(0, DC);
end;
end;
procedure TKCustomHexEditor.UpdateMouseCursor;
var
P: TPoint;
begin
P := ScreenToClient(Mouse.CursorPos);
SetMouseCursor(P.X, P.Y);
end;
procedure TKCustomHexEditor.UpdateScrollRange;
var
I: Integer;
AD: TKHexEditorAreaDimensions;
SI: TScrollInfo;
begin
if HandleAllocated then
begin
AD := GetAreaDimensions;
// update horizontal scroll position
I := FLeftChar - GetMaxLeftChar(AD.TotalHorz);
if I > 0 then
Dec(FLeftChar, I);
FLeftChar := Max(FLeftChar, 0);
// update vertical scroll position
I := FTopLine - GetMaxTopLine(AD.TotalVert);
if I > 0 then
Dec(FTopLine, I);
FTopLine := Max(FTopLine, 0);
if FScrollBars in [ssBoth, ssHorizontal, ssVertical] then
begin
SI.cbSize := SizeOf(TScrollInfo);
SI.fMask := SIF_RANGE or SIF_PAGE or SIF_POS {$IFDEF UNIX}or SIF_UPDATEPOLICY{$ENDIF};
SI.nMin := 0;
{$IFDEF UNIX}
SI.ntrackPos := SB_POLICY_CONTINUOUS;
{$ENDIF}
if FScrollBars in [ssBoth, ssHorizontal] then
begin
SI.nMax := AD.TotalHorz{$IFNDEF FPC}- 1{$ENDIF};
SI.nPage := GetClientWidthChars;
SI.nPos := FLeftChar;
SetScrollInfo(Handle, SB_HORZ, SI, True);
ShowScrollBar(Handle, SB_HORZ, Integer(SI.nPage) < AD.TotalHorz);
end else
ShowScrollBar(Handle, SB_HORZ, False);
if FScrollBars in [ssBoth, ssVertical] then
begin
SI.nMax := AD.TotalVert{$IFNDEF FPC}- 1{$ENDIF};
SI.nPage := GetClientHeightChars;
SI.nPos := FTopLine;
SetScrollInfo(Handle, SB_VERT, SI, True);
ShowScrollBar(Handle, SB_VERT, Integer(SI.nPage) < AD.TotalVert);
end else
ShowScrollBar(Handle, SB_VERT, False);
end;
UpdateEditorCaret(True);
Invalidate;
InvalidatePageSetup;
end;
end;
procedure TKCustomHexEditor.UpdateSelEnd(Point: TPoint; ClipToClient: Boolean);
var
R: TRect;
Sel: TKHexEditorSelection;
begin
if ClipToClient then
begin
R := GetModifiedClientRect;
Dec(R.Right, FCharWidth);
Dec(R.Bottom, FCharHeight);
if CanScroll(ecScrollLeft) and (Point.X < R.Left) then
Point.X := R.Left
else if CanScroll(ecScrollRight) and (Point.X > R.Right) then
Point.X := R.Right;
if CanScroll(ecScrollUp) and (Point.Y < R.Top) then
Point.Y := R.Top
else if CanScroll(ecScrollDown) and (Point.Y > R.Bottom) then
Point.Y := R.Bottom;
end;
Sel := PointToSel(Point, True, FEditArea);
if (Sel.Index <> cInvalidIndex) and
((Sel.Index <> FSelEnd.Index) or (Sel.Digit <> FSelEnd.Digit)) then
begin
FSelEnd := Sel;
UpdateEditorCaret;
Invalidate;
InvalidatePageSetup;
end;
end;
procedure TKCustomHexEditor.UpdateSize;
begin
UpdateScrollRange;
end;
procedure TKCustomHexEditor.ValidateSelection(var Value: TKHexEditorSelection; Area: TKHexEditorArea);
begin
if Area <> eaNone then
begin
Value.Index := MinMax(Value.Index, 0, FSize);
if Value.Index = FSize then
Value.Digit := 0
else
Value.Digit := MinMax(Value.Digit, 0, cDigitCount - 1);
end else
Value := MakeSelection(cInvalidIndex, 0);
end;
{$IFNDEF FPC}
procedure TKCustomHexEditor.WMDropFiles(var Msg: TLMessage);
var
I, FileCount: Integer;
PathName: array[0..260] of Char;
Point: TPoint;
FilesList: TStringList;
begin
try
if Assigned(FOnDropFiles) then
begin
FilesList := TStringList.Create;
try
FileCount := DragQueryFile(THandle(Msg.wParam), Cardinal(-1), nil, 0);
DragQueryPoint(THandle(Msg.wParam), Point);
for i := 0 to FileCount - 1 do
begin
DragQueryFile(THandle(Msg.wParam), I, PathName, SizeOf(PathName));
FilesList.Add(PathName);
end;
FOnDropFiles(Self, Point.X, Point.Y, FilesList);
finally
FilesList.Free;
end;
end;
finally
Msg.Result := 0;
DragFinish(THandle(Msg.wParam));
end;
end;
{$ENDIF}
procedure TKCustomHexEditor.WMEraseBkgnd(var Msg: TLMessage);
begin
Msg.Result := 1;
end;
procedure TKCustomHexEditor.WMGetDlgCode(var Msg: TLMNoParams);
begin
Msg.Result := DLGC_WANTARROWS;
end;
procedure TKCustomHexEditor.WMHScroll(var Msg: TLMHScroll);
begin
SafeSetFocus;
ModifyScrollBar(SB_HORZ, Msg.ScrollCode, Msg.Pos, True);
end;
procedure TKCustomHexEditor.WMKillFocus(var Msg: TLMKillFocus);
begin
inherited;
ExecuteCommand(ecLostFocus);
end;
procedure TKCustomHexEditor.WMSetFocus(var Msg: TLMSetFocus);
begin
inherited;
ExecuteCommand(ecGotFocus);
end;
procedure TKCustomHexEditor.WMVScroll(var Msg: TLMVScroll);
begin
SafeSetFocus;
ModifyScrollBar(SB_VERT, Msg.ScrollCode, Msg.Pos, True);
end;
function GetColorSpec(Index: TKHexEditorColorIndex): TKHexEditorColorSpec;
begin
case Index of
ciAddressText: begin Result.Def := cAddressTextDef; Result.Name := sAddressText; end;
ciAddressBkGnd: begin Result.Def := cAddressBkgndDef; Result.Name := sAddressBkGnd; end;
ciBkGnd: begin Result.Def := cBkGndDef; Result.Name := sBkGnd; end;
ciDigitTextEven: begin Result.Def := cDigitTextEvenDef; Result.Name := sDigitTextEven; end;
ciDigitTextOdd: begin Result.Def := cDigitTextOddDef; Result.Name := sDigitTextOdd; end;
ciDigitBkGnd: begin Result.Def := cDigitBkGndDef; Result.Name := sDigitBkgnd; end;
ciHorzLines: begin Result.Def := cHorzLinesDef; Result.Name := sHorzLines; end;
ciInactiveCaretBkGnd: begin Result.Def := cInactiveCaretBkGndDef; Result.Name := sInactiveCaretBkGnd; end;
ciInactiveCaretSelBkGnd: begin Result.Def := cInactiveCaretSelBkGndDef; Result.Name := sInactiveCaretSelBkGnd; end;
ciInactiveCaretSelText: begin Result.Def := cInactiveCaretSelTextDef; Result.Name := sInactiveCaretSelText; end;
ciInactiveCaretText: begin Result.Def := cInactiveCaretTextDef; Result.Name := sInactiveCaretText; end;
ciLinesHighLight: begin Result.Def := cLinesHighLightDef; Result.Name := sLinesHighLight; end;
ciSelBkGnd: begin Result.Def := cSelBkGndDef; Result.Name := sSelBkGnd; end;
ciSelBkGndFocused: begin Result.Def := cSelBkGndFocusedDef; Result.Name := sSelBkGndFocused; end;
ciSelText: begin Result.Def := cSelTextDef; Result.Name := sSelText; end;
ciSelTextFocused: begin Result.Def := cSelTextFocusedDef; Result.Name := sSelTextFocused; end;
ciSeparators: begin Result.Def := cSeparatorsDef; Result.Name := sSeparators; end;
ciTextText: begin Result.Def := cTextTextDef; Result.Name := sTextText; end;
ciTextBkGnd: begin Result.Def := cTextBkgndDef; Result.Name := sTextBkGnd; end;
ciVertLines: begin Result.Def := cVertLinesDef; Result.Name := sVertLines; end;
else
Result.Def := clNone;
Result.Name := '';
end;
end;
end.