lazarus/components/synedit/syngutter.pp
2009-11-08 17:14:33 +00:00

437 lines
12 KiB
ObjectPascal

unit SynGutter;
{$I synedit.inc}
interface
uses
SysUtils, Classes, Controls, Graphics, LCLType, LCLIntf, Menus,
SynEditMarks, SynEditMiscClasses, SynEditMiscProcs, SynEditFoldedView,
SynTextDrawer, SynGutterBase, SynGutterLineNumber, SynGutterCodeFolding,
SynGutterMarks, SynGutterChanges, SynEditMouseCmds;
type
TSynGutterSeparator = class;
{ TSynGutter }
TSynGutter = class(TSynGutterBase)
private
// List of all gutters
FEdit: TSynEditBase;
FWidth: integer;
FRightOffset, FLeftOffset: integer;
FOnChange: TNotifyEvent;
FCursor: TCursor;
FVisible: boolean;
FAutoSize: boolean;
FOnGutterClick: TGutterClickEvent;
FMouseDownPart: Integer;
procedure SetAutoSize(const Value: boolean);
procedure SetLeftOffset(Value: integer);
procedure SetRightOffset(Value: integer);
procedure SetVisible(Value: boolean);
procedure SetWidth(Value: integer);
function PixelToPartIndex(X: Integer): Integer;
protected
procedure DoChange(Sender: TObject); override;
procedure DoDefaultGutterClick(Sender: TObject; X, Y, Line: integer;
mark: TSynEditMark); override;
public
property SynEdit: TSynEditBase read FEdit;
public
constructor Create(AOwner : TSynEditBase; AFoldedLinesView: TSynEditFoldedView;
ATextDrawer: TheTextDrawer);
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
procedure Paint(Canvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer);
function RealGutterWidth(CharWidth: integer): 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;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
// A Methods for access from the Gutter; to be replaced
procedure AutoSizeDigitCount(LinesCount: integer); // Forward to Line Number
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: boolean read FAutoSize write SetAutoSize default True;
property Color;
property Cursor: TCursor read FCursor write FCursor default crDefault;
property LeftOffset: integer read FLeftOffset write SetLeftOffset
default 0;
property RightOffset: integer read FRightOffset write SetRightOffset
default 0;
property Visible: boolean read FVisible write SetVisible default TRUE;
property Width: integer read FWidth write SetWidth default 30;
property Parts;
property MouseActions;
end;
{ TSynGutterSeparator }
TSynGutterSeparator = class(TSynGutterPartBase)
public
constructor Create(AOwner: TComponent); override;
procedure Paint(Canvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer); override;
function RealGutterWidth(CharWidth: integer): integer; override;
end;
{ TSynEditMouseActionsGutter }
TSynEditMouseActionsGutter = class(TSynEditMouseActions)
public
procedure ResetDefaults; override;
end;
implementation
uses
SynEdit;
{ TSynGutter }
constructor TSynGutter.Create(AOwner: TSynEditBase;
AFoldedLinesView: TSynEditFoldedView; ATextDrawer: TheTextDrawer);
begin
FEdit := AOwner;
inherited Create(AOwner, AFoldedLinesView, ATextDrawer);
FMouseActions := TSynEditMouseActionsGutter.Create(self);
FMouseActions.ResetDefaults;
Visible := True;
Width := 30;
LeftOffset := 0;
FRightOffset := 0;
AutoSize := True;
if not(csLoading in AOwner.ComponentState) then begin
// 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;
end;
destructor TSynGutter.Destroy;
begin
FreeAndNil(FMouseActions);
FOnChange := nil;
inherited Destroy;
end;
procedure TSynGutter.Assign(Source: TPersistent);
var
Src: TSynGutter;
begin
inherited;
if Assigned(Source) and (Source is TSynGutter) then
begin
Src := TSynGutter(Source);
FVisible := Src.FVisible;
FWidth := Src.FWidth;
FRightOffset := Src.FRightOffset;
FAutoSize := Src.FAutoSize;
DoChange(Self);
end else
end;
function TSynGutter.RealGutterWidth(CharWidth: integer): integer;
var
i: Integer;
begin
if not FVisible then
begin
Result := 0;
Exit;
end;
Result := FLeftOffset + FRightOffset;
for i := PartCount-1 downto 0 do
Result := Result + Parts[i].RealGutterWidth(CharWidth);
end;
procedure TSynGutter.SetLeftOffset(Value: integer);
begin
Value := Max(0, Value);
if FLeftOffset <> Value then
begin
FLeftOffset := Value;
DoChange(Self);
end;
end;
procedure TSynGutter.SetAutoSize(const Value: boolean);
begin
if FAutoSize <> Value then
begin
FAutoSize := Value;
DoChange(Self);
end;
end;
procedure TSynGutter.SetRightOffset(Value: integer);
begin
Value := Max(0, Value);
if FRightOffset <> Value then
begin
FRightOffset := Value;
DoChange(Self);
end;
end;
procedure TSynGutter.SetVisible(Value: boolean);
begin
if FVisible <> Value then
begin
FVisible := Value;
DoChange(Self);
end;
end;
procedure TSynGutter.SetWidth(Value: integer);
begin
if FAutoSize then
Value := RealGutterWidth(TextDrawer.CharWidth);
Value := Max(0, Value);
if (FWidth <> Value) then
begin
FWidth := Value;
DoChange(Self);
end;
end;
function TSynGutter.PixelToPartIndex(X: Integer): Integer;
begin
Result := 0;
x := x - FLeftOffset;
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.DoChange(Sender: TObject);
begin
If FAutoSize then
FWidth := RealGutterWidth(TextDrawer.CharWidth);
if Assigned(FOnChange) then
FOnChange(Self);
end;
procedure TSynGutter.DoDefaultGutterClick(Sender: TObject; X, Y, Line: integer;
mark: TSynEditMark);
begin
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,Canvas.Brush.Color);
{$ENDIF}
// Clear all
TextDrawer.BeginDrawing(dc);
TextDrawer.SetBackColor(Color);
TextDrawer.SetForeColor(TSynEdit(FEdit).Font.Color);
TextDrawer.SetFrameColor(clNone);
with AClip do
TextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, AClip, nil, 0);
TextDrawer.EndDrawing;
AClip.Left := FLeftOffset;
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;
procedure TSynGutter.AutoSizeDigitCount(LinesCount : integer);
var
i: Integer;
begin
for i := 0 to Parts.ByClassCount[TSynGutterLineNumber] - 1 do
TSynGutterLineNumber(Parts.ByClass[TSynGutterLineNumber, i]).AutoSizeDigitCount(LinesCount);
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 }
constructor TSynGutterSeparator.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FWidth := 2;
MarkupInfo.Background := clWhite;
MarkupInfo.Foreground := clDkGray;
end;
procedure TSynGutterSeparator.Paint(Canvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer);
begin
with Canvas do
begin
Pen.Color := MarkupInfo.Background;
Pen.Width := 1;
with AClip do
begin
MoveTo(AClip.Left, AClip.Top);
LineTo(AClip.Left, AClip.Bottom);
Pen.Color := MarkupInfo.Foreground;
MoveTo(AClip.Left+1, AClip.Top);
LineTo(AClip.Left+1, AClip.Bottom);
end;
end;
end;
function TSynGutterSeparator.RealGutterWidth(CharWidth: integer): integer;
begin
If Visible then
Result := Width
else
Result := 0;
end;
{ TSynEditMouseActionsGutter }
procedure TSynEditMouseActionsGutter.ResetDefaults;
begin
Clear;
AddCommand(emcOnMainGutterClick, False, mbLeft, ccAny, cdDown, [], []);
AddCommand(emcContextMenu, False, mbRight, ccSingle, cdUp, [], []);
end;
end.