lazarus/components/synedit/syngutterlinenumber.pp

323 lines
9.9 KiB
ObjectPascal

unit SynGutterLineNumber;
{$I synedit.inc}
interface
uses
Classes, SysUtils, Graphics, LCLType, LCLIntf, SynGutterBase,
SynEditMiscProcs, SynTextDrawer, SynEditMouseCmds,
LazSynEditText, SynEditTypes;
type
TSynGutterLineNumber = class;
TSynEditGutterLineInfo = record
ShowDot: boolean;
ViewLine: Integer;
LineRange: TLineRange;
end;
TSynEditGetGutterLineTextEvent = procedure(Sender: TSynGutterLineNumber; ALine: integer; out AText: string; const ALineInfo: TSynEditGutterLineInfo) of object;
TSynEditMouseActionsLineNum = class(TSynEditMouseInternalActions)
public // empty by default
end;
{ TSynGutterLineNumber }
TSynGutterLineNumber = class(TSynGutterPartBase)
private
FTextDrawer: TheTextDrawer;
FDigitCount: integer;
FAutoSizeDigitCount: integer;
FShowOnlyLineNumbersMultiplesOf: integer;
FLeadingZeros: boolean;
FZeroStart: boolean;
FOnFormatLineNumber: TSynEditGetGutterLineTextEvent;
procedure SetDigitCount(AValue : integer);
procedure SetLeadingZeros(const AValue : boolean);
procedure SetShowOnlyLineNumbersMultiplesOf(const AValue : integer);
procedure SetZeroStart(const AValue : boolean);
protected
procedure Init; override;
function PreferedWidthAtCurrentPPI: Integer; override;
procedure LineCountChanged(Sender: TSynEditStrings; AIndex, ACount: Integer);
procedure BufferChanged(Sender: TObject);
procedure FontChanged(Sender: TObject);
procedure SetAutoSize(const AValue : boolean); override;
procedure SetVisible(const AValue: boolean); override;
function CreateMouseActions: TSynEditMouseInternalActions; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
function FormatLineNumber(Line: integer; IsDot: boolean): string;
procedure Paint(Canvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer);
override;
published
property MarkupInfo;
property MarkupInfoCurrentLine;
property DigitCount: integer read FDigitCount write SetDigitCount;
property ShowOnlyLineNumbersMultiplesOf: integer
read FShowOnlyLineNumbersMultiplesOf write SetShowOnlyLineNumbersMultiplesOf;
property ZeroStart: boolean read FZeroStart write SetZeroStart;
property LeadingZeros: boolean read FLeadingZeros write SetLeadingZeros;
property OnFormatLineNumber: TSynEditGetGutterLineTextEvent read FOnFormatLineNumber write FOnFormatLineNumber;
end;
implementation
{ TSynGutterLineNumber }
constructor TSynGutterLineNumber.Create(AOwner: TComponent);
begin
FDigitCount := 2;
FAutoSizeDigitCount := FDigitCount;
FShowOnlyLineNumbersMultiplesOf := 1;
FLeadingZeros := false;
FZeroStart := False;
inherited Create(AOwner);
end;
procedure TSynGutterLineNumber.Init;
begin
inherited Init;
FTextDrawer := Gutter.TextDrawer;
ViewedTextBuffer.AddChangeHandler(senrLineCount, @LineCountChanged);
ViewedTextBuffer.AddNotifyHandler(senrTextBufferChanged, @BufferChanged);
FTextDrawer.RegisterOnFontChangeHandler(@FontChanged);
LineCountchanged(nil, 0, 0);
end;
destructor TSynGutterLineNumber.Destroy;
begin
ViewedTextBuffer.RemoveHandlers(self);
FTextDrawer.UnRegisterOnFontChangeHandler(@FontChanged);
inherited Destroy;
end;
procedure TSynGutterLineNumber.Assign(Source : TPersistent);
var
Src: TSynGutterLineNumber;
begin
if Source is TSynGutterLineNumber then
begin
Src := TSynGutterLineNumber(Source);
FLeadingZeros := Src.FLeadingZeros;
FZeroStart := Src.FZeroStart;
FDigitCount := Src.FDigitCount;
FAutoSizeDigitCount := Src.FAutoSizeDigitCount;
FShowOnlyLineNumbersMultiplesOf := Src.FShowOnlyLineNumbersMultiplesOf;
end;
inherited;
end;
procedure TSynGutterLineNumber.SetDigitCount(AValue : integer);
begin
AValue := MinMax(AValue, 2, 99);
if FDigitCount <> AValue then
begin
FDigitCount := AValue;
if AutoSize then begin
FAutoSizeDigitCount := Max(FDigitCount, FAutoSizeDigitCount);
DoAutoSize;
end else
FAutoSizeDigitCount := FDigitCount;
DoChange(Self);
end;
end;
procedure TSynGutterLineNumber.SetLeadingZeros(const AValue : boolean);
begin
if FLeadingZeros <> AValue then
begin
FLeadingZeros := AValue;
DoChange(Self);
end;
end;
procedure TSynGutterLineNumber.SetShowOnlyLineNumbersMultiplesOf(const AValue : integer);
begin
If FShowOnlyLineNumbersMultiplesOf <> AValue then
begin
FShowOnlyLineNumbersMultiplesOf := Max(AValue, 1);
DoChange(self);
end;
end;
procedure TSynGutterLineNumber.SetZeroStart(const AValue : boolean);
begin
if FZeroStart <> AValue then
begin
FZeroStart := AValue;
if AutoSize then
DoAutoSize;
DoChange(Self);
end;
end;
function TSynGutterLineNumber.FormatLineNumber(Line: integer; IsDot: boolean): string;
var
i: integer;
begin
Result := '';
// if a dot must be showed
if IsDot then
if Line mod 5 = 0 then // every 5 lines show '-' instead of '.'
Result := StringOfChar(' ', FAutoSizeDigitCount-1) + '-'
else
Result := StringOfChar(' ', FAutoSizeDigitCount-1) + '.'
// else format the line number
else begin
if FZeroStart then Dec(Line);
Str(Line : FAutoSizeDigitCount, Result);
if FLeadingZeros then
for i := 1 to FAutoSizeDigitCount - 1 do begin
if (Result[i] <> ' ') then break;
Result[i] := '0';
end;
end;
end;
function TSynGutterLineNumber.PreferedWidthAtCurrentPPI: Integer;
begin
Result := FAutoSizeDigitCount * FTextDrawer.CharWidth + 1;
end;
procedure TSynGutterLineNumber.LineCountChanged(Sender: TSynEditStrings; AIndex, ACount: Integer);
var
LineCnt, nDigits: integer;
begin
if not(Visible and AutoSize) then exit;
LineCnt := TextBuffer.Count;
if FZeroStart and (LineCnt > 0) then Dec(LineCnt);
nDigits := Max(Length(IntToStr(LineCnt)), FDigitCount);
if FAutoSizeDigitCount <> nDigits then begin
FAutoSizeDigitCount := nDigits;
DoAutoSize;
end;
end;
procedure TSynGutterLineNumber.BufferChanged(Sender: TObject);
begin
LineCountChanged(nil, 0, 0);
end;
procedure TSynGutterLineNumber.FontChanged(Sender: TObject);
begin
DoAutoSize;
end;
procedure TSynGutterLineNumber.SetAutoSize(const AValue: boolean);
begin
inherited SetAutoSize(AValue);
if AValue then LineCountChanged(nil, 0, 0);
end;
procedure TSynGutterLineNumber.SetVisible(const AValue: boolean);
begin
inherited SetVisible(AValue);
if AValue then LineCountChanged(nil, 0, 0);
end;
function TSynGutterLineNumber.CreateMouseActions: TSynEditMouseInternalActions;
begin
Result := TSynEditMouseActionsLineNum.Create(self);
end;
procedure TSynGutterLineNumber.Paint(Canvas : TCanvas; AClip : TRect; FirstLine, LastLine : integer);
var
i, c, iLine: integer;
rcLine: TRect;
s: string;
dc: HDC;
LineInfo: TSynEditGutterLineInfo;
LineHeight, EveryNLine: Integer;
t: TLinePos;
begin
if not Visible then exit;
LineHeight := SynEdit.LineHeight;
c := SynEdit.Lines.Count;
t := ToIdx(GutterArea.TextArea.TopLine);
// Changed to use fTextDrawer.BeginDrawing and fTextDrawer.EndDrawing only
// when absolutely necessary. Note: Never change brush / pen / font of the
// canvas inside of this block (only through methods of fTextDrawer)!
dc := Canvas.Handle;
FTextDrawer.BeginDrawing(dc);
try
FTextDrawer.SetBackColor(MarkupInfoInternal.Background);
fTextDrawer.SetForeColor(MarkupInfoInternal.Foreground);
fTextDrawer.SetFrameColor(MarkupInfoInternal.FrameColor);
fTextDrawer.Style := MarkupInfoInternal.Style;
// prepare the rect initially
rcLine := AClip;
rcLine.Bottom := AClip.Top;
EveryNLine := Max(ShowOnlyLineNumbersMultiplesOf, 1);
for i := t + FirstLine to t + LastLine do
begin
iLine := ToPos(ViewedTextBuffer.DisplayView.ViewToTextIndexEx(i, LineInfo.LineRange));
if (iLine < 1) or (iLine > c) then break;
// next line rect
rcLine.Top := rcLine.Bottom;
// Must show a dot instead of line number if
// line number is not the first, the last, the current line
// or a multiple of ShowOnlyLineNumbersMultiplesOf
LineInfo.ShowDot := ((iLine mod EveryNLine) <> 0)
and (iLine <> SynEdit.CaretY) and (iLine <> 1)
and (iLine <> SynEdit.Lines.Count);
// Get the formatted line number or dot
if Assigned(FOnFormatLineNumber) then begin
LineInfo.ViewLine := i;
FOnFormatLineNumber(Self, iLine, s, LineInfo);
end
else begin
s := FormatLineNumber(iLine, LineInfo.ShowDot);
end;
Inc(rcLine.Bottom, LineHeight);
if i <> LineInfo.LineRange.Top then
s := '';
// erase the background and draw the line number string in one go
if i - t = CaretRow then begin
FTextDrawer.SetBackColor(MarkupInfoCurLineMerged.Background);
fTextDrawer.SetForeColor(MarkupInfoCurLineMerged.Foreground);
fTextDrawer.SetFrameColor(MarkupInfoCurLineMerged.FrameColor);
fTextDrawer.Style := MarkupInfoCurLineMerged.Style;
end
else
if i - t = CaretRow+1 then begin
FTextDrawer.SetBackColor(MarkupInfoInternal.Background);
fTextDrawer.SetForeColor(MarkupInfoInternal.Foreground);
fTextDrawer.SetFrameColor(MarkupInfoInternal.FrameColor);
fTextDrawer.Style := MarkupInfoInternal.Style;
end;
fTextDrawer.ExtTextOut(rcLine.Left, rcLine.Top, ETO_OPAQUE or ETO_CLIPPED, rcLine,
PChar(Pointer(S)),Length(S));
end;
// now erase the remaining area if any
if AClip.Bottom > rcLine.Bottom then
begin
FTextDrawer.SetBackColor(MarkupInfoInternal.Background);
fTextDrawer.SetForeColor(MarkupInfoInternal.Foreground);
fTextDrawer.SetFrameColor(MarkupInfoInternal.FrameColor);
fTextDrawer.Style := MarkupInfoInternal.Style;
rcLine.Top := rcLine.Bottom;
rcLine.Bottom := AClip.Bottom;
with rcLine do
fTextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, rcLine, nil, 0);
end;
finally
fTextDrawer.EndDrawing;
end;
end;
end.