lazarus/components/synedit/syneditmarks.pp
2009-06-21 19:30:24 +00:00

393 lines
9.9 KiB
ObjectPascal

unit SynEditMarks;
{$I synedit.inc}
interface
uses
Classes, SysUtils, math, SynEditMiscClasses, SynEditTextBase;
const
// Max number of book/gutter marks returned from GetEditMarksForLine - that
// really should be enough.
maxMarks = 16;
type
{ TSynEditMark }
TSynEditMark = class
protected
FLine, FColumn, FImage, FPriority: Integer;
FEdit: TSynEditBase;
FVisible: boolean;
FInternalImage: boolean;
FBookmarkNum: integer;
function GetEdit: TSynEditBase; virtual;
procedure SetColumn(const Value: Integer); virtual;
procedure SetImage(const Value: Integer); virtual;
procedure SetLine(const Value: Integer); virtual;
procedure SetPriority(const AValue: integer); virtual;
procedure SetVisible(const Value: boolean); virtual; //MWE: Laz needs to know when a line gets visible, so the editor color can be updated
procedure SetInternalImage(const Value: boolean);
function GetIsBookmark: boolean;
public
constructor Create(AOwner: TSynEditBase);
property Line: integer read FLine write SetLine;
property Column: integer read FColumn write SetColumn;
property Priority: integer read FPriority write SetPriority;
property ImageIndex: integer read FImage write SetImage;
property BookmarkNumber: integer read FBookmarkNum write fBookmarkNum;
property Visible: boolean read FVisible write SetVisible;
property InternalImage: boolean read FInternalImage write SetInternalImage;
property IsBookmark: boolean read GetIsBookmark;
end;
TPlaceMarkEvent = procedure(Sender: TObject; var Mark: TSynEditMark) of object;
TSynEditMarks = array[1..maxMarks] of TSynEditMark;
{ A list of mark objects. Each object cause a litle picture to be drawn in the
gutter. }
{ TSynEditMarkList }
TSynEditMarkList = class(TList)
protected
FEdit: TSynEditBase;
FLines: TSynEditStrings;
fOnChange: TNotifyEvent;
procedure DoChange;
function Get(Index: Integer): TSynEditMark;
procedure Put(Index: Integer; Item: TSynEditMark);
procedure DoLinesEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount,
aLineBrkCnt: Integer; aText: String);
public
constructor Create(AOwner: TSynEditBase; ALines: TSynEditStrings);
destructor Destroy; override;
function Add(Item: TSynEditMark): Integer;
procedure ClearLine(line: integer);
procedure Delete(Index: Integer);
function First: TSynEditMark;
procedure GetMarksForLine(line: integer; var Marks: TSynEditMarks);
procedure Insert(Index: Integer; Item: TSynEditMark);
function Last: TSynEditMark;
procedure Place(Mark: TSynEditMark);
function Remove(Item: TSynEditMark): Integer;
public
property Items[Index: Integer]: TSynEditMark read Get write Put; default;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;
function DoMarksCompareBookmarksFirst(Item1, Item2: Pointer): Integer;
function DoMarksCompareBookmarksLast(Item1, Item2: Pointer): Integer;
implementation
uses SynEdit;
type // This is until InvalidateGutterLines, can be moved to an accessible place
SynEditAccess = Class(TCustomSynEdit);
function DoMarksCompareBookmarksFirst(Item1, Item2: Pointer): Integer;
var
Mark1: TSynEditMark absolute Item1;
Mark2: TSynEditMark absolute Item2;
begin
Result := 0;
if Mark1 = Mark2 then Exit;
if Mark1.IsBookmark then
Result := -1
else
if Mark2.IsBookmark then
Result := 1
else
if Mark1.Priority < Mark2.Priority then
Result := 1
else
if Mark1.Priority > Mark2.Priority then
Result := -1;
end;
function DoMarksCompareBookmarksLast(Item1, Item2: Pointer): Integer;
var
Mark1: TSynEditMark absolute Item1;
Mark2: TSynEditMark absolute Item2;
begin
Result := 0;
if Mark1 = Mark2 then Exit;
if Mark1.IsBookmark then
Result := 1
else
if Mark2.IsBookmark then
Result := -1
else
if Mark1.Priority < Mark2.Priority then
Result := 1
else
if Mark1.Priority > Mark2.Priority then
Result := -1;
end;
procedure SortMarks(var Marks: TSynEditMarks; Compare: TListSortCompare);
var
i, j, LastMark: Integer;
P: Pointer;
begin
for i := Low(Marks) to High(Marks) do
if Marks[i] = nil then
begin
LastMark := i - 1;
break;
end;
// insert sort is the best for our items count
for i := Low(Marks) + 1 to LastMark do
begin
P := Marks[i];
j := i - 1;
while (j >= Low(Marks)) and (Compare(P, Marks[j]) < 1) do
begin
Marks[j + 1] := Marks[j];
j := j - 1;
end;
Marks[j + 1] := TSynEditMark(P);
end;
end;
{ TSynEditMark }
procedure TSynEditMark.SetPriority(const AValue: integer);
begin
FPriority := AValue;
end;
function TSynEditMark.GetEdit: TSynEditBase;
begin
if FEdit <> nil then try
if TCustomSynEdit(FEdit).Marks.IndexOf(self) = -1 then
FEdit := nil;
except
FEdit := nil;
end;
Result := FEdit;
end;
function TSynEditMark.GetIsBookmark: boolean;
begin
Result := (fBookmarkNum >= 0);
end;
procedure TSynEditMark.SetColumn(const Value: Integer);
begin
FColumn := Value;
end;
procedure TSynEditMark.SetImage(const Value: Integer);
begin
FImage := Value;
if FVisible and Assigned(FEdit) then
SynEditAccess(Pointer(FEdit)).InvalidateGutterLines(FLine, FLine);
end;
procedure TSynEditMark.SetInternalImage(const Value: boolean);
begin
FInternalImage := Value;
if FVisible and Assigned(FEdit) then
SynEditAccess(Pointer(FEdit)).InvalidateGutterLines(FLine, FLine);
end;
procedure TSynEditMark.SetLine(const Value: Integer);
begin
if FVisible and Assigned(FEdit) then
begin
if FLine > 0 then
SynEditAccess(Pointer(FEdit)).InvalidateGutterLines(FLine, FLine);
FLine := Value;
SynEditAccess(Pointer(FEdit)).InvalidateGutterLines(FLine, FLine);
end else
FLine := Value;
end;
procedure TSynEditMark.SetVisible(const Value: boolean);
begin
if FVisible <> Value then
begin
FVisible := Value;
if Assigned(FEdit) then
SynEditAccess(Pointer(FEdit)).InvalidateGutterLines(FLine, FLine);
end;
end;
constructor TSynEditMark.Create(AOwner: TSynEditBase);
begin
inherited Create;
FBookmarkNum := -1;
FEdit := AOwner;
FPriority := 0;
end;
{ TSynEditMarkList }
function TSynEditMarkList.Add(Item: TSynEditMark): Integer;
begin
Result := inherited Add(Item);
DoChange;
end;
procedure TSynEditMarkList.ClearLine(Line: integer);
var
i: integer;
begin
for i := Count - 1 downto 0 do
if not Items[i].IsBookmark and (Items[i].Line = Line) then Delete(i);
end;
constructor TSynEditMarkList.Create(AOwner: TSynEditBase; ALines: TSynEditStrings);
begin
inherited Create;
FEdit := AOwner;
FLines := ALines;
FLines.AddEditHandler(@DoLinesEdited);
end;
destructor TSynEditMarkList.Destroy;
var
i: integer;
begin
FLines.RemoveEditHandler(@DoLinesEdited);
for i := 0 to Pred(Count) do
Get(i).Free;
inherited Destroy;
end;
procedure TSynEditMarkList.Delete(Index: Integer);
begin
inherited Delete(Index);
DoChange;
end;
procedure TSynEditMarkList.DoChange;
begin
if Assigned(FOnChange) then
FOnChange(Self);
end;
function TSynEditMarkList.First: TSynEditMark;
begin
Result := TSynEditMark(inherited First);
end;
function TSynEditMarkList.Get(Index: Integer): TSynEditMark;
begin
Result := TSynEditMark(inherited Get(Index));
end;
//Returns up to maxMarks book/gutter marks for a chosen line.
procedure TSynEditMarkList.GetMarksForLine(line: integer;
var marks: TSynEditMarks);
var
cnt: integer;
i: integer;
begin
FillChar(marks, SizeOf(marks), 0);
cnt := 0;
for i := 0 to Count - 1 do
begin
if Items[i].Line = line then
begin
Inc(cnt);
marks[cnt] := Items[i];
if cnt = maxMarks then break;
end;
end;
if Assigned(FEdit) then
if TSynEdit(FEdit).BookMarkOptions.DrawBookmarksFirst then
SortMarks(marks, @DoMarksCompareBookmarksFirst)
else
SortMarks(marks, @DoMarksCompareBookmarksLast);
end;
procedure TSynEditMarkList.Insert(Index: Integer; Item: TSynEditMark);
begin
inherited Insert(Index, Item);
DoChange;
end;
function TSynEditMarkList.Last: TSynEditMark;
begin
Result := TSynEditMark(inherited Last);
end;
procedure TSynEditMarkList.Place(mark: TSynEditMark);
begin
if assigned(FEdit) then
if assigned(TSynEdit(FEdit).OnPlaceBookmark) then
TSynEdit(FEdit).OnPlaceBookmark(TSynEdit(FEdit), mark);
if assigned(mark) then
Add(mark);
DoChange;
end;
procedure TSynEditMarkList.Put(Index: Integer; Item: TSynEditMark);
begin
inherited Put(Index, Item);
DoChange;
end;
procedure TSynEditMarkList.DoLinesEdited(Sender: TSynEditStrings; aLinePos, aBytePos, aCount,
aLineBrkCnt: Integer; aText: String);
var
i, Col: Integer;
begin
if Count = 0 then exit;
Col := FLines.LogicalToPhysicalPos(Point(aBytePos, aLinePos)).x;
if aLineBrkCnt > 0 then
begin
for i := 0 to Count - 1 do
if (Items[i].Line > aLinePos) then
Items[i].Line := Items[i].Line + aLineBrkCnt
else
if (Items[i].Line = aLinePos) and (Items[i].Column > col) then begin
Items[i].Line := Items[i].Line + aLineBrkCnt;
Items[i].Column := Items[i].Column - Col + 1;
end;
end
else
if aLineBrkCnt < 0 then
begin
for i := 0 to Count - 1 do
if (Items[i].Line > aLinePos - aLineBrkCnt) then
Items[i].Line := Items[i].Line + aLineBrkCnt
else
if (Items[i].Line > aLinePos) then begin
Items[i].Line := aLinePos;
Items[i].Column := Items[i].Column + Col - 1;
end;
end
else
if aCount > 0 then
begin
for i := 0 to Count - 1 do
if (Items[i].Line = aLinePos) and (Items[i].Column > col) then
Items[i].Column := Items[i].Column + aCount;
end
else
if aCount < 0 then
begin
for i := 0 to Count - 1 do
if (Items[i].Line = aLinePos) and (Items[i].Column > col) then
Items[i].Column := Max(Col, Items[i].Column + aCount);
end;
end;
function TSynEditMarkList.Remove(Item: TSynEditMark): Integer;
begin
Result := inherited Remove(Item);
DoChange;
end;
end.