mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-07 14:38:01 +02:00
492 lines
15 KiB
ObjectPascal
492 lines
15 KiB
ObjectPascal
unit SynGutter;
|
|
|
|
{$I synedit.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, Classes, Controls, Graphics, LCLType, LCLIntf, Menus,
|
|
SynEditMarks, SynEditTypes, SynEditMiscClasses, SynEditMiscProcs, LazSynTextArea,
|
|
SynTextDrawer, SynGutterBase, SynGutterLineNumber, SynGutterCodeFolding,
|
|
SynGutterMarks, SynGutterChanges, SynEditMouseCmds, SynGutterLineOverview;
|
|
|
|
type
|
|
|
|
TSynGutterSeparator = class;
|
|
TLazSynGutterArea = class;
|
|
|
|
{ TSynGutter }
|
|
|
|
TSynGutter = class(TSynGutterBase)
|
|
private
|
|
FCursor: TCursor;
|
|
FOnGutterClick: TGutterClickEvent;
|
|
FMouseDownPart: Integer;
|
|
function PixelToPartIndex(X: Integer): Integer;
|
|
protected
|
|
procedure DoDefaultGutterClick(Sender: TObject; X, Y, Line: integer;
|
|
mark: TSynEditMark); override;
|
|
function CreatePartList: TSynGutterPartListBase; override;
|
|
procedure CreateDefaultGutterParts; virtual;
|
|
function CreateMouseActions: TSynEditMouseInternalActions; override;
|
|
property GutterArea;
|
|
public
|
|
constructor Create(AOwner : TSynEditBase; ASide: TSynGutterSide;
|
|
ATextDrawer: TheTextDrawer);
|
|
destructor Destroy; override;
|
|
procedure Paint(Canvas: TCanvas; Surface:TLazSynGutterArea; AClip: TRect; FirstLine, LastLine: integer);
|
|
function HasCustomPopupMenu(out PopMenu: TPopupMenu): Boolean;
|
|
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
procedure MouseMove(Shift: TShiftState; X, Y: Integer);
|
|
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
function MaybeHandleMouseAction(var AnInfo: TSynEditMouseActionInfo;
|
|
HandleActionProc: TSynEditMouseActionHandler): Boolean; override;
|
|
function DoHandleMouseAction(AnAction: TSynEditMouseAction;
|
|
var AnInfo: TSynEditMouseActionInfo): Boolean;
|
|
procedure ResetMouseActions; override; // set mouse-actions according to current Options / may clear them
|
|
procedure DoOnGutterClick(X, Y: integer);
|
|
property OnGutterClick: TGutterClickEvent
|
|
read FOnGutterClick write FOnGutterClick;
|
|
public
|
|
// Access to well known parts
|
|
Function LineNumberPart(Index: Integer = 0): TSynGutterLineNumber;
|
|
Function CodeFoldPart(Index: Integer = 0): TSynGutterCodeFolding;
|
|
Function ChangesPart(Index: Integer = 0): TSynGutterChanges;
|
|
Function MarksPart(Index: Integer = 0): TSynGutterMarks;
|
|
Function SeparatorPart(Index: Integer = 0): TSynGutterSeparator;
|
|
Function LineOverviewPart(Index: Integer = 0): TSynGutterLineOverview;
|
|
published
|
|
property AutoSize;
|
|
property Color;
|
|
property CurrentLineColor;
|
|
property Cursor: TCursor read FCursor write FCursor default crDefault;
|
|
property LeftOffset;
|
|
property RightOffset;
|
|
property Visible;
|
|
property Width;
|
|
property Parts;
|
|
property MouseActions;
|
|
property OnResize;
|
|
property OnChange;
|
|
end;
|
|
|
|
{ TSynGutterSeparator }
|
|
|
|
TSynGutterSeparator = class(TSynGutterPartBase)
|
|
private
|
|
FLineOffset: Integer;
|
|
FLineOnRight: Boolean;
|
|
FLineWidth: Integer;
|
|
procedure SetLineOffset(const AValue: Integer);
|
|
procedure SetLineOnRight(const AValue: Boolean);
|
|
procedure SetLineWidth(const AValue: Integer);
|
|
protected
|
|
function PreferedWidth: Integer; override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
procedure Paint(Canvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer); override;
|
|
published
|
|
property LineWidth: Integer read FLineWidth write SetLineWidth default 1;
|
|
property LineOffset: Integer read FLineOffset write SetLineOffset default 0;
|
|
property LineOnRight: Boolean read FLineOnRight write SetLineOnRight default True;
|
|
property MarkupInfo;
|
|
property MarkupInfoCurrentLine;
|
|
end;
|
|
|
|
{ TSynEditMouseActionsGutter }
|
|
|
|
TSynEditMouseActionsGutter = class(TSynEditMouseInternalActions)
|
|
protected
|
|
procedure InitForOptions(AnOptions: TSynEditorMouseOptions); override;
|
|
end;
|
|
|
|
{ TLazSynGutterArea }
|
|
|
|
TLazSynGutterArea = class(TLazSynSurfaceWithText)
|
|
private
|
|
FGutter: TSynGutter;
|
|
function GetTextBounds: TRect;
|
|
procedure SetGutter(AValue: TSynGutter);
|
|
protected
|
|
procedure DoPaint(ACanvas: TCanvas; AClip: TRect); override;
|
|
public
|
|
procedure InvalidateLines(FirstTextLine, LastTextLine: TLineIdx; AScreenLineOffset: Integer = 0); override;
|
|
procedure Assign(Src: TLazSynSurface); override;
|
|
property Gutter: TSynGutter read FGutter write SetGutter;
|
|
property TextBounds: TRect read GetTextBounds;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TLazSynGutterArea }
|
|
|
|
function TLazSynGutterArea.GetTextBounds: TRect;
|
|
begin
|
|
Result := TextArea.TextBounds;
|
|
end;
|
|
|
|
procedure TLazSynGutterArea.SetGutter(AValue: TSynGutter);
|
|
begin
|
|
if FGutter = AValue then Exit;
|
|
if FGutter <> nil then
|
|
FGutter.GutterArea := nil;
|
|
FGutter := AValue;
|
|
if FGutter <> nil then
|
|
FGutter.GutterArea := Self;
|
|
end;
|
|
|
|
procedure TLazSynGutterArea.DoPaint(ACanvas: TCanvas; AClip: TRect);
|
|
var
|
|
ScreenRow1, ScreenRow2: integer;
|
|
begin
|
|
// TODO TextArea top/bottom padding;
|
|
ScreenRow1 := Max((AClip.Top - Bounds.Top) div TextArea.LineHeight, 0);
|
|
ScreenRow2 := Min((AClip.Bottom-1 - Bounds.Top) div TextArea.LineHeight, TextArea.LinesInWindow + 1);
|
|
|
|
FGutter.Paint(ACanvas, Self, AClip, ScreenRow1, ScreenRow2);
|
|
end;
|
|
|
|
procedure TLazSynGutterArea.InvalidateLines(FirstTextLine,
|
|
LastTextLine: TLineIdx; AScreenLineOffset: Integer);
|
|
var
|
|
rcInval: TRect;
|
|
begin
|
|
rcInval := Bounds;
|
|
if (FirstTextLine >= 0) then
|
|
rcInval.Top := Max(TextArea.TextBounds.Top,
|
|
TextArea.TextBounds.Top
|
|
+ (DisplayView.TextToViewIndex(FirstTextLine).Top + AScreenLineOffset
|
|
- TextArea.TopLine + 1) * TextArea.LineHeight);
|
|
if (LastTextLine >= 0) then
|
|
rcInval.Bottom := Min(TextArea.TextBounds.Bottom,
|
|
TextArea.TextBounds.Top
|
|
+ (DisplayView.TextToViewIndex(LastTextLine).Bottom + AScreenLineOffset
|
|
- TextArea.TopLine + 2) * TextArea.LineHeight);
|
|
|
|
{$IFDEF VerboseSynEditInvalidate}
|
|
DebugLn(['TCustomSynEdit.InvalidateGutterLines ',DbgSName(self), ' FirstLine=',FirstTextLine, ' LastLine=',LastTextLine, ' rect=',dbgs(rcInval)]);
|
|
{$ENDIF}
|
|
if (rcInval.Top < rcInval.Bottom) and (rcInval.Left < rcInval.Right) then
|
|
InvalidateRect(Handle, @rcInval, FALSE);
|
|
end;
|
|
|
|
procedure TLazSynGutterArea.Assign(Src: TLazSynSurface);
|
|
begin
|
|
inherited Assign(Src);
|
|
FGutter := TLazSynGutterArea(Src).FGutter;
|
|
end;
|
|
|
|
{ TSynGutter }
|
|
|
|
constructor TSynGutter.Create(AOwner: TSynEditBase; ASide: TSynGutterSide;
|
|
ATextDrawer: TheTextDrawer);
|
|
begin
|
|
inherited;
|
|
if not(csLoading in AOwner.ComponentState) then
|
|
CreateDefaultGutterParts;
|
|
end;
|
|
|
|
destructor TSynGutter.Destroy;
|
|
begin
|
|
OnChange := nil;
|
|
OnResize := nil;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TSynGutter.CreateDefaultGutterParts;
|
|
begin
|
|
if Side <> gsLeft then
|
|
exit;
|
|
|
|
// Todo: currently there is only one Gutter so names can be fixed
|
|
with TSynGutterMarks.Create(Parts) do
|
|
Name := 'SynGutterMarks1';
|
|
with TSynGutterLineNumber.Create(Parts) do
|
|
Name := 'SynGutterLineNumber1';
|
|
with TSynGutterChanges.Create(Parts) do
|
|
Name := 'SynGutterChanges1';
|
|
with TSynGutterSeparator.Create(Parts) do
|
|
Name := 'SynGutterSeparator1';
|
|
with TSynGutterCodeFolding.Create(Parts) do
|
|
Name := 'SynGutterCodeFolding1';
|
|
end;
|
|
|
|
function TSynGutter.CreateMouseActions: TSynEditMouseInternalActions;
|
|
begin
|
|
Result := TSynEditMouseActionsGutter.Create(self);
|
|
end;
|
|
|
|
function TSynGutter.PixelToPartIndex(X: Integer): Integer;
|
|
begin
|
|
Result := 0;
|
|
x := x - Left - LeftOffset;
|
|
while Result < PartCount-1 do begin
|
|
if Parts[Result].Visible then begin
|
|
if x >= Parts[Result].FullWidth then
|
|
x := x - Parts[Result].FullWidth
|
|
else
|
|
break;
|
|
end;
|
|
inc(Result)
|
|
end;
|
|
end;
|
|
|
|
procedure TSynGutter.DoDefaultGutterClick(Sender: TObject; X, Y, Line: integer;
|
|
mark: TSynEditMark);
|
|
begin
|
|
end;
|
|
|
|
function TSynGutter.CreatePartList: TSynGutterPartListBase;
|
|
begin
|
|
case Side of
|
|
gsLeft:
|
|
begin
|
|
Result := TSynGutterPartList.Create(SynEdit, self); //left side gutter
|
|
Result.Name := 'SynLeftGutterPartList1';
|
|
end;
|
|
gsRight:
|
|
begin
|
|
Result := TSynRightGutterPartList.Create(SynEdit, self);
|
|
Result.Name := 'SynRightGutterPartList1';
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynGutter.DoOnGutterClick(X, Y: integer);
|
|
begin
|
|
Parts[PixelToPartIndex(X)].DoOnGutterClick(X, Y);
|
|
end;
|
|
|
|
procedure TSynGutter.Paint(Canvas: TCanvas; Surface:TLazSynGutterArea; AClip: TRect; FirstLine, LastLine: integer);
|
|
var
|
|
aCaretRow: LongInt;
|
|
i, t: integer;
|
|
rcLine, rcClip: TRect;
|
|
dc: HDC;
|
|
begin
|
|
aCaretRow := ToIdx(SynEdit.TextXYToScreenXY(SynEdit.CaretXY).Y);
|
|
if (aCaretRow < FirstLine) or (aCaretRow > LastLine) then
|
|
aCaretRow := -1;
|
|
FCaretRow := aCaretRow;
|
|
|
|
Canvas.Brush.Color := Color;
|
|
dc := Canvas.Handle;
|
|
LCLIntf.SetBkColor(dc, TColorRef(Canvas.Brush.Color));
|
|
|
|
rcClip := AClip;
|
|
t := Surface.TextBounds.Top;
|
|
// Clear all
|
|
TextDrawer.BeginDrawing(dc);
|
|
TextDrawer.SetBackColor(Color);
|
|
TextDrawer.SetForeColor(SynEdit.Font.Color);
|
|
TextDrawer.SetFrameColor(clNone);
|
|
if aCaretRow >= 0 then
|
|
rcClip.Bottom := t + aCaretRow * SynEdit.LineHeight;
|
|
with rcClip do
|
|
TextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, rcClip, nil, 0);
|
|
if aCaretRow >= 0 then begin
|
|
rcClip.top := rcClip.Bottom + SynEdit.LineHeight;
|
|
rcClip.Bottom := AClip.Bottom;
|
|
with rcClip do
|
|
TextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, rcClip, nil, 0);
|
|
|
|
rcClip.Bottom := rcClip.Top;
|
|
rcClip.top := rcClip.Top - SynEdit.LineHeight;
|
|
TextDrawer.SetBackColor(MarkupInfoCurLineMerged.Background);
|
|
with rcClip do
|
|
TextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, rcClip, nil, 0);
|
|
end;
|
|
TextDrawer.EndDrawing;
|
|
|
|
AClip.Left := Surface.Left + LeftOffset;
|
|
AClip.Top := t + FirstLine * SynEdit.LineHeight;
|
|
|
|
rcLine := AClip;
|
|
rcLine.Right := rcLine.Left;
|
|
for i := 0 to PartCount -1 do
|
|
begin
|
|
if rcLine.Right >= AClip.Right then break;
|
|
if Parts[i].Visible then
|
|
begin
|
|
rcLine.Left := rcLine.Right;
|
|
rcLine.Right := min(rcLine.Left + Parts[i].FullWidth, AClip.Right);
|
|
Parts[i].PaintAll(Canvas, rcLine, FirstLine, LastLine);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TSynGutter.HasCustomPopupMenu(out PopMenu: TPopupMenu): Boolean;
|
|
begin
|
|
Result := Parts[FMouseDownPart].HasCustomPopupMenu(PopMenu);
|
|
end;
|
|
|
|
procedure TSynGutter.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
FMouseDownPart := PixelToPartIndex(X);
|
|
Parts[FMouseDownPart].MouseDown(Button, Shift, X, Y);
|
|
if (Button=Controls.mbLeft) then
|
|
DoOnGutterClick(X, Y);
|
|
end;
|
|
|
|
procedure TSynGutter.MouseMove(Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
Parts[FMouseDownPart].MouseMove(Shift, X, Y);
|
|
end;
|
|
|
|
procedure TSynGutter.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
Parts[FMouseDownPart].MouseUp(Button, Shift, X, Y);
|
|
end;
|
|
|
|
function TSynGutter.MaybeHandleMouseAction(var AnInfo: TSynEditMouseActionInfo;
|
|
HandleActionProc: TSynEditMouseActionHandler): Boolean;
|
|
var
|
|
MouseDownPart: LongInt;
|
|
begin
|
|
MouseDownPart := PixelToPartIndex(AnInfo.MouseX);
|
|
if MouseDownPart < PartCount then
|
|
Result := Parts[MouseDownPart].MaybeHandleMouseAction(AnInfo, HandleActionProc)
|
|
else
|
|
Result := False;
|
|
if not Result then
|
|
Result := inherited MaybeHandleMouseAction(AnInfo, HandleActionProc);
|
|
end;
|
|
|
|
function TSynGutter.DoHandleMouseAction(AnAction: TSynEditMouseAction;
|
|
var AnInfo: TSynEditMouseActionInfo): Boolean;
|
|
var
|
|
i: Integer;
|
|
ACommand: Word;
|
|
begin
|
|
Result := False;
|
|
for i := 0 to Parts.Count - 1 do begin
|
|
Result := Parts[i].DoHandleMouseAction(AnAction, AnInfo);
|
|
if Result then exit;;
|
|
end;
|
|
|
|
if AnAction = nil then exit;
|
|
ACommand := AnAction.Command;
|
|
if (ACommand = emcNone) then exit;
|
|
|
|
case ACommand of
|
|
emcOnMainGutterClick:
|
|
begin
|
|
if Assigned(FOnGutterClick) then begin
|
|
FOnGutterClick(Self, AnInfo.MouseX, AnInfo.MouseY, AnInfo.NewCaret.LinePos, nil);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynGutter.ResetMouseActions;
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := 0 to Parts.Count - 1 do
|
|
Parts[i].ResetMouseActions;
|
|
|
|
inherited;
|
|
end;
|
|
|
|
function TSynGutter.LineNumberPart(Index: Integer = 0): TSynGutterLineNumber;
|
|
begin
|
|
Result := TSynGutterLineNumber(Parts.ByClass[TSynGutterLineNumber, Index]);
|
|
end;
|
|
|
|
function TSynGutter.CodeFoldPart(Index: Integer = 0): TSynGutterCodeFolding;
|
|
begin
|
|
Result := TSynGutterCodeFolding(Parts.ByClass[TSynGutterCodeFolding, Index]);
|
|
end;
|
|
|
|
function TSynGutter.ChangesPart(Index: Integer = 0): TSynGutterChanges;
|
|
begin
|
|
Result := TSynGutterChanges(Parts.ByClass[TSynGutterChanges, Index]);
|
|
end;
|
|
|
|
function TSynGutter.MarksPart(Index: Integer = 0): TSynGutterMarks;
|
|
begin
|
|
Result := TSynGutterMarks(Parts.ByClass[TSynGutterMarks, Index]);
|
|
end;
|
|
|
|
function TSynGutter.SeparatorPart(Index: Integer = 0): TSynGutterSeparator;
|
|
begin
|
|
Result := TSynGutterSeparator(Parts.ByClass[TSynGutterSeparator, Index]);
|
|
end;
|
|
|
|
function TSynGutter.LineOverviewPart(Index: Integer): TSynGutterLineOverview;
|
|
begin
|
|
Result := TSynGutterLineOverview(Parts.ByClass[TSynGutterLineOverview, Index]);
|
|
end;
|
|
|
|
{ TSynGutterSeparator }
|
|
|
|
procedure TSynGutterSeparator.SetLineWidth(const AValue: Integer);
|
|
begin
|
|
if FLineWidth = AValue then exit;
|
|
FLineWidth := AValue;
|
|
DoChange(Self);
|
|
end;
|
|
|
|
procedure TSynGutterSeparator.SetLineOffset(const AValue: Integer);
|
|
begin
|
|
if FLineOffset = AValue then exit;
|
|
FLineOffset := AValue;
|
|
DoChange(Self);
|
|
end;
|
|
|
|
procedure TSynGutterSeparator.SetLineOnRight(const AValue: Boolean);
|
|
begin
|
|
if FLineOnRight = AValue then exit;
|
|
FLineOnRight := AValue;
|
|
DoChange(Self);
|
|
end;
|
|
|
|
function TSynGutterSeparator.PreferedWidth: Integer;
|
|
begin
|
|
Result := 2;
|
|
end;
|
|
|
|
constructor TSynGutterSeparator.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
MarkupInfo.Background := clWhite;
|
|
MarkupInfo.Foreground := clDkGray;
|
|
FLineWidth := 1;
|
|
FLineOffset := 0;
|
|
FLineOnRight := True;
|
|
end;
|
|
|
|
procedure TSynGutterSeparator.Paint(Canvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer);
|
|
begin
|
|
PaintBackground(Canvas, AClip);
|
|
|
|
if FLineOnRight then begin
|
|
AClip.Right := Min(AClip.Right, Left + LeftOffset + Width - FLineOffset);
|
|
AClip.Left := Max(AClip.Left, Left + LeftOffset + Width - FLineOffset - FLineWidth);
|
|
end else begin
|
|
AClip.Left := Max(AClip.Left, Left + LeftOffset + FLineOffset);
|
|
AClip.Right := Min(AClip.Right, Left + LeftOffset + FLineOffset + FLineWidth);
|
|
end;
|
|
if AClip.Right > AClip.Left then begin
|
|
Canvas.Brush.Color := MarkupInfo.Foreground;
|
|
Canvas.FillRect(AClip);
|
|
end;
|
|
end;
|
|
|
|
{ TSynEditMouseActionsGutter }
|
|
|
|
procedure TSynEditMouseActionsGutter.InitForOptions(AnOptions: TSynEditorMouseOptions);
|
|
var
|
|
rmc: Boolean;
|
|
begin
|
|
Clear;
|
|
rmc := (emRightMouseMovesCursor in AnOptions);
|
|
|
|
AddCommand(emcOnMainGutterClick, False, mbXLeft, ccAny, cdDown, [], []);
|
|
AddCommand(emcContextMenu, rmc, mbXRight, ccSingle, cdUp, [], []);
|
|
end;
|
|
|
|
end.
|
|
|