mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-12 17:38:01 +02:00
373 lines
10 KiB
ObjectPascal
373 lines
10 KiB
ObjectPascal
unit SynGutter;
|
|
|
|
{$I synedit.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, Classes, Controls, Graphics, LCLType, LCLIntf, Menus,
|
|
SynEditMarks, SynEditMiscClasses, SynEditMiscProcs,
|
|
SynTextDrawer, SynGutterBase, SynGutterLineNumber, SynGutterCodeFolding,
|
|
SynGutterMarks, SynGutterChanges, SynEditMouseCmds;
|
|
|
|
type
|
|
|
|
TSynGutterSeparator = 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;
|
|
public
|
|
constructor Create(AOwner : TSynEditBase; ASide: TSynGutterSide;
|
|
ATextDrawer: TheTextDrawer);
|
|
destructor Destroy; override;
|
|
procedure Paint(Canvas: TCanvas; 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;
|
|
function DoHandleMouseAction(AnAction: TSynEditMouseAction;
|
|
var AnInfo: TSynEditMouseActionInfo): Boolean;
|
|
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;
|
|
published
|
|
property AutoSize;
|
|
property Color;
|
|
property Cursor: TCursor read FCursor write FCursor default crDefault;
|
|
property LeftOffset;
|
|
property RightOffset;
|
|
property Visible;
|
|
property Width;
|
|
property Parts;
|
|
property MouseActions;
|
|
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;
|
|
end;
|
|
|
|
{ TSynEditMouseActionsGutter }
|
|
|
|
TSynEditMouseActionsGutter = class(TSynEditMouseActions)
|
|
public
|
|
procedure ResetDefaults; override;
|
|
end;
|
|
|
|
|
|
implementation
|
|
uses
|
|
SynEdit;
|
|
|
|
{ TSynGutter }
|
|
|
|
constructor TSynGutter.Create(AOwner: TSynEditBase; ASide: TSynGutterSide;
|
|
ATextDrawer: TheTextDrawer);
|
|
begin
|
|
inherited;
|
|
|
|
FMouseActions := TSynEditMouseActionsGutter.Create(self);
|
|
FMouseActions.ResetDefaults;
|
|
|
|
if not(csLoading in AOwner.ComponentState) then
|
|
CreateDefaultGutterParts;
|
|
end;
|
|
|
|
destructor TSynGutter.Destroy;
|
|
begin
|
|
OnChange := nil;
|
|
OnResize := nil;
|
|
FreeAndNil(FMouseActions);
|
|
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.PixelToPartIndex(X: Integer): Integer;
|
|
begin
|
|
Result := 0;
|
|
x := x - LeftOffset;
|
|
while Result < PartCount-1 do begin
|
|
if Parts[Result].Visible then begin
|
|
if x >= Parts[Result].Width then
|
|
x := x - Parts[Result].Width
|
|
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; AClip: TRect; FirstLine, LastLine: integer);
|
|
var
|
|
i: integer;
|
|
rcLine: TRect;
|
|
dc: HDC;
|
|
begin
|
|
Canvas.Brush.Color := Color;
|
|
dc := Canvas.Handle;
|
|
{$IFDEF SYN_LAZARUS}
|
|
LCLIntf.SetBkColor(dc, TColorRef(Canvas.Brush.Color));
|
|
{$ENDIF}
|
|
|
|
// Clear all
|
|
TextDrawer.BeginDrawing(dc);
|
|
TextDrawer.SetBackColor(Color);
|
|
TextDrawer.SetForeColor(TCustomSynEdit(SynEdit).Font.Color);
|
|
TextDrawer.SetFrameColor(clNone);
|
|
with AClip do
|
|
TextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, AClip, nil, 0);
|
|
TextDrawer.EndDrawing;
|
|
|
|
if Side = gsLeft then
|
|
AClip.Left := LeftOffset
|
|
else
|
|
AClip.Left := SynEdit.ClientWidth - Width - ScrollBarWidth + LeftOffset;
|
|
|
|
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].Width, AClip.Right);
|
|
Parts[i].Paint(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=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);
|
|
Result := Parts[MouseDownPart].MaybeHandleMouseAction(AnInfo, HandleActionProc);
|
|
if not Result then
|
|
Result := HandleActionProc(MouseActions, AnInfo);
|
|
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;
|
|
|
|
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;
|
|
|
|
{ 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
|
|
with Canvas do
|
|
begin
|
|
Brush.Color := MarkupInfo.Background;
|
|
Brush.Style := bsSolid;
|
|
FillRect(AClip);
|
|
if FLineOnRight then begin
|
|
dec(AClip.Right, FLineOffset);
|
|
AClip.Left := AClip.Right - FLineWidth;
|
|
end else begin
|
|
inc(AClip.Left, FLineOffset);
|
|
AClip.Right := AClip.Left + FLineWidth;
|
|
end;
|
|
Brush.Color := MarkupInfo.Foreground;
|
|
FillRect(AClip);
|
|
end;
|
|
end;
|
|
|
|
{ TSynEditMouseActionsGutter }
|
|
|
|
procedure TSynEditMouseActionsGutter.ResetDefaults;
|
|
begin
|
|
Clear;
|
|
AddCommand(emcOnMainGutterClick, False, mbLeft, ccAny, cdDown, [], []);
|
|
AddCommand(emcContextMenu, False, mbRight, ccSingle, cdUp, [], []);
|
|
end;
|
|
|
|
end.
|
|
|