lazarus/components/synedit/syngutterlinenumber.pp

275 lines
7.9 KiB
ObjectPascal

unit SynGutterLineNumber;
{$I synedit.inc}
interface
uses
Classes, SysUtils, Graphics, LCLType, LCLIntf, SynGutter,
SynEditMiscProcs, SynEditMiscClasses, SynTextDrawer, SynEditFoldedView;
type
{ TSynGutterLineNumber }
TSynGutterLineNumber = class(TSynGutterPartBase)
private
FEdit: TSynEditBase;
FFoldView: TSynEditFoldedView;
FTextDrawer: TheTextDrawer;
FMarkupInfoLineNumber: TSynSelectedColor;
FDigitCount: integer;
FAutoSizeDigitCount: integer;
FShowOnlyLineNumbersMultiplesOf: integer;
FLeadingZeros: boolean;
FZeroStart: boolean;
procedure SetDigitCount(AValue : integer);
procedure SetLeadingZeros(const AValue : boolean);
procedure SetShowOnlyLineNumbersMultiplesOf(const AValue : integer);
procedure SetZeroStart(const AValue : boolean);
function FormatLineNumber(Line: integer; IsDot: boolean): string;
public
constructor Create(AOwner : TSynEditBase; AFoldView : TSynEditFoldedView;
ATextDrawer: TheTextDrawer);
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
procedure Paint(Canvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer);
override;
procedure AutoSizeDigitCount(LinesCount: integer);
function RealGutterWidth(CharWidth: integer): integer; override;
public
property MarkupInfoLineNumber: TSynSelectedColor read FMarkupInfoLineNumber;
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;
end;
implementation
uses
SynEdit;
{ TSynGutterLineNumber }
constructor TSynGutterLineNumber.Create(AOwner : TSynEditBase;
AFoldView : TSynEditFoldedView; ATextDrawer : TheTextDrawer);
begin
inherited Create;
FEdit := AOwner;
FFoldView := AFoldView;
FTextDrawer := ATextDrawer;
FDigitCount := 2;
FAutoSizeDigitCount := FDigitCount;
FShowOnlyLineNumbersMultiplesOf := 1;
FLeadingZeros := false;
FZeroStart := False;
FMarkupInfoLineNumber := TSynSelectedColor.Create;
FMarkupInfoLineNumber.Background := clNone;
FMarkupInfoLineNumber.Foreground := clNone;
FMarkupInfoLineNumber.OnChange := @DoChange;
end;
destructor TSynGutterLineNumber.Destroy;
begin
FMarkupInfoLineNumber.Free;
inherited Destroy;
end;
procedure TSynGutterLineNumber.Assign(Source : TPersistent);
var
Src: TSynGutterLineNumber;
begin
if Assigned(Source) and (Source is TSynGutterLineNumber) then
begin
Src := TSynGutterLineNumber(Source);
FLeadingZeros := Src.FLeadingZeros;
FZeroStart := Src.FZeroStart;
FDigitCount := Src.FDigitCount;
FAutoSizeDigitCount := Src.FAutoSizeDigitCount;
FShowOnlyLineNumbersMultiplesOf := Src.FShowOnlyLineNumbersMultiplesOf;
FMarkupInfoLineNumber.Assign(Src.MarkupInfoLineNumber);
end;
inherited;
end;
procedure TSynGutterLineNumber.SetDigitCount(AValue : integer);
begin
AValue := MinMax(AValue, 2, 12);
if FDigitCount <> AValue then
begin
FDigitCount := AValue;
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 := AValue;
DoChange(self);
end;
end;
procedure TSynGutterLineNumber.SetZeroStart(const AValue : boolean);
begin
if FZeroStart <> AValue then
begin
FZeroStart := AValue;
DoChange(Self);
end;
end;
function TSynGutterLineNumber.RealGutterWidth(CharWidth : integer) : integer;
begin
if not Visible then
begin
Result := 0;
Exit;
end;
if AutoSize then
Width := FAutoSizeDigitCount * CharWidth + 1;
Result := Width;
end;
procedure TSynGutterLineNumber.AutoSizeDigitCount(LinesCount: integer);
var
nDigits: integer;
begin
if Visible and AutoSize then
begin
if FZeroStart then Dec(LinesCount);
nDigits := Max(Length(IntToStr(LinesCount)), FDigitCount);
if FAutoSizeDigitCount <> nDigits then
begin
FAutoSizeDigitCount := nDigits;
DoChange(Self);
end;
end else
FAutoSizeDigitCount := FDigitCount;
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;
procedure TSynGutterLineNumber.Paint(Canvas : TCanvas; AClip : TRect; FirstLine, LastLine : integer);
var
i, iLine: integer;
rcLine: TRect;
s: string;
dc: HDC;
ShowDot: boolean;
LineHeight: Integer;
begin
if not Visible then exit;
LineHeight := TSynEdit(FEdit).LineHeight;
// 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)!
Canvas.Brush.Color := Color;
dc := Canvas.Handle;
{$IFDEF SYN_LAZARUS}
LCLIntf.SetBkColor(dc,Canvas.Brush.Color);
{$ENDIF}
fTextDrawer.BeginDrawing(dc);
try
if MarkupInfoLineNumber.Background <> clNone then
fTextDrawer.SetBackColor(MarkupInfoLineNumber.Background)
else
fTextDrawer.SetBackColor(Color);
if MarkupInfoLineNumber.Foreground <> clNone then
fTextDrawer.SetForeColor(MarkupInfoLineNumber.Foreground)
else
fTextDrawer.SetForeColor(TSynEdit(FEdit).Font.Color);
fTextDrawer.SetFrameColor(MarkupInfoLineNumber.FrameColor);
fTextDrawer.Style := MarkupInfoLineNumber.Style;
// prepare the rect initially
rcLine := AClip;
rcLine.Bottom := FirstLine * LineHeight;
for i := FirstLine to LastLine do
begin
iLine := FFoldView.DisplayNumber[i];
// 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
ShowDot := ((iLine mod ShowOnlyLineNumbersMultiplesOf) <> 0)
and (iLine <> TSynEdit(FEdit).CaretY) and (iLine <> 1)
and (iLine <> TSynEdit(FEdit).Lines.Count);
// Get the formatted line number or dot
s := FormatLineNumber(iLine, ShowDot);
Inc(rcLine.Bottom, LineHeight);
// erase the background and draw the line number string in one go
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
rcLine.Top := rcLine.Bottom;
rcLine.Bottom := AClip.Bottom;
with rcLine do
fTextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, rcLine, nil, 0);
end;
// restore original style
fTextDrawer.SetBackColor(Color);
fTextDrawer.SetForeColor(TSynEdit(FEdit).Font.Color);
fTextDrawer.SetFrameColor(clNone);
if AClip.Left < rcLine.Left then
begin
rcLine.Right := rcLine.Left;
rcLine.Left := AClip.Left;
rcLine.Top := AClip.Top;
rcLine.Bottom := AClip.Bottom;
with rcLine do
fTextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, rcLine, nil, 0);
end;
finally
fTextDrawer.EndDrawing;
end;
end;
end.