mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-06-21 22:28:22 +02:00
390 lines
8.7 KiB
ObjectPascal
390 lines
8.7 KiB
ObjectPascal
{
|
|
/***************************************************************************
|
|
CocoaCaret.pas - Cocoa Caret Emulation
|
|
------------------------------------------
|
|
|
|
copyright (c) Andreas Hausladen
|
|
|
|
adopted for Lazarus and Cocoa by Lazarus Team
|
|
|
|
***************************************************************************/
|
|
|
|
*****************************************************************************
|
|
This file is part of the Lazarus Component Library (LCL)
|
|
|
|
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
|
for details about the license.
|
|
*****************************************************************************
|
|
}
|
|
|
|
unit CocoaCaret;
|
|
{$mode objfpc}{$H+}
|
|
{$modeswitch objectivec1}
|
|
|
|
interface
|
|
|
|
uses
|
|
// Bindings
|
|
CocoaAll,
|
|
// Free Pascal
|
|
Classes, SysUtils, Types,
|
|
// Widgetset
|
|
CocoaGDIObjects, CocoaPrivate;
|
|
|
|
type
|
|
|
|
{ TEmulatedCaret }
|
|
|
|
TEmulatedCaret = class(TObject)
|
|
private
|
|
FTimerTarget: NSObject;
|
|
FTimer: NSTimer;
|
|
FOldRect: TRect;
|
|
FView: NSView;
|
|
FBitmap: TCocoaBitmap;
|
|
FWidth, FHeight: Integer;
|
|
FPos: TPoint;
|
|
FVisible: Boolean;
|
|
FVisibleState: Boolean;
|
|
FWidgetSetReleased: Boolean;
|
|
FHideCount: Integer;
|
|
procedure SetPos(const Value: TPoint);
|
|
procedure ResetTimer;
|
|
protected
|
|
procedure DoTimer(Sender: TObject);
|
|
procedure DrawCaret; virtual;
|
|
procedure SetView(AView: NSView);
|
|
procedure InvalidateView;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
function CreateCaret(AView: NSView; Bitmap: PtrUInt; Width, Height: Integer): Boolean;
|
|
function DestroyCaret: Boolean;
|
|
|
|
function IsValid: Boolean;
|
|
|
|
function Show: Boolean;
|
|
function Hide: Boolean;
|
|
|
|
property Pos: TPoint read FPos write SetPos;
|
|
end;
|
|
|
|
function CreateCaret(View: NSView; Bitmap: PtrUInt; Width, Height: Integer): Boolean; overload;
|
|
function HideCaret(View: NSView): Boolean;
|
|
function ShowCaret(View: NSView): Boolean;
|
|
function SetCaretPos(X, Y: Integer): Boolean;
|
|
function GetCaretPos(var P: TPoint): Boolean;
|
|
function DestroyCaret(View: NSView): Boolean;
|
|
procedure DrawCaret;
|
|
procedure DestroyGlobalCaret;
|
|
|
|
implementation
|
|
|
|
type
|
|
{ TCaretTimerTarget }
|
|
|
|
TCaretTimerTarget = objcclass(NSObject)
|
|
fCaret: TEmulatedCaret;
|
|
procedure CaretEvent(sender: id); message 'CaretEvent:';
|
|
end;
|
|
|
|
var
|
|
GlobalCaret: TEmulatedCaret = nil;
|
|
|
|
procedure GlobalCaretNeeded;
|
|
begin
|
|
if GlobalCaret = nil then GlobalCaret := TEmulatedCaret.Create;
|
|
end;
|
|
|
|
procedure DrawCaret;
|
|
begin
|
|
GlobalCaretNeeded;
|
|
if Assigned(GlobalCaret) then
|
|
GlobalCaret.DrawCaret;
|
|
end;
|
|
|
|
procedure DestroyGlobalCaret;
|
|
begin
|
|
FreeAndNil(GlobalCaret);
|
|
end;
|
|
|
|
function CreateCaret(View: NSView; Bitmap: PtrUInt; Width, Height: Integer): Boolean;
|
|
begin
|
|
GlobalCaretNeeded;
|
|
|
|
if Assigned(GlobalCaret) then
|
|
Result := GlobalCaret.CreateCaret(View, Bitmap, Width, Height)
|
|
else
|
|
Result := false;
|
|
end;
|
|
|
|
function GetCaretBlinkTime: Cardinal;
|
|
begin
|
|
// TODO: use MacOSAll.GetCaretTime
|
|
Result := 600; // our default value
|
|
end;
|
|
|
|
function HideCaret(View: NSView): Boolean;
|
|
begin
|
|
Result := False;
|
|
GlobalCaretNeeded;
|
|
|
|
if Assigned(GlobalCaret) then
|
|
begin
|
|
Result := not Assigned(View) or (View = GlobalCaret.FView);
|
|
if Result then
|
|
Result := GlobalCaret.Hide;
|
|
end;
|
|
end;
|
|
|
|
function ShowCaret(View: NSView): Boolean;
|
|
begin
|
|
//writeln('ShowCaret: ', PtrUInt(View));
|
|
Result := False;
|
|
GlobalCaretNeeded;
|
|
|
|
if Assigned(GlobalCaret) then
|
|
begin
|
|
Result := not Assigned(View) or (view = GlobalCaret.FView);
|
|
if Result then
|
|
Result := GlobalCaret.Show;
|
|
end;
|
|
end;
|
|
|
|
function SetCaretPos(X, Y: Integer): Boolean;
|
|
begin
|
|
Result := True;
|
|
GlobalCaretNeeded;
|
|
if Assigned(GlobalCaret) then
|
|
GlobalCaret.Pos := Classes.Point(X, Y);
|
|
end;
|
|
|
|
function GetCaretPos(var P: TPoint): Boolean;
|
|
begin
|
|
Result := True;
|
|
GlobalCaretNeeded;
|
|
|
|
if Assigned(GlobalCaret) then
|
|
begin
|
|
with GlobalCaret.Pos do
|
|
begin
|
|
P.x := X;
|
|
P.y := Y;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function DestroyCaret(View: NSView): Boolean;
|
|
begin
|
|
Result := False;
|
|
|
|
if Assigned(GlobalCaret) then
|
|
begin
|
|
Result := not Assigned(View) or (GlobalCaret.FView = View);
|
|
if Result then
|
|
Result := GlobalCaret.DestroyCaret;
|
|
end;
|
|
end;
|
|
|
|
procedure CocoaDisableTimer(var ATimer: NSTimer);
|
|
begin
|
|
if not Assigned(ATimer) then Exit;
|
|
ATimer.invalidate;
|
|
ATimer := nil;
|
|
end;
|
|
|
|
function CocoaEnableTimer(trg: id): NSTimer;
|
|
begin
|
|
Result:=NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats(
|
|
GetCaretBlinkTime / 1000, trg, ObjCSelector('CaretEvent:'), nil, true);
|
|
end;
|
|
|
|
{ TCaretTimerTarget }
|
|
|
|
procedure TCaretTimerTarget.CaretEvent(sender: id);
|
|
begin
|
|
if not Assigned(fCaret) then Exit;
|
|
fCaret.DoTimer(nil);
|
|
end;
|
|
|
|
{ TEmulatedCaret }
|
|
|
|
constructor TEmulatedCaret.Create;
|
|
begin
|
|
inherited Create;
|
|
|
|
FOldRect := Rect(0, 0, 1, 1);
|
|
|
|
FTimerTarget := TCaretTimerTarget.alloc.init;
|
|
TCaretTimerTarget(FTimerTarget).fCaret := Self;
|
|
end;
|
|
|
|
destructor TEmulatedCaret.Destroy;
|
|
begin
|
|
DestroyCaret;
|
|
FTimerTarget.release;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TEmulatedCaret.CreateCaret(AView: NSView; Bitmap: PtrUInt;
|
|
Width, Height: Integer): Boolean;
|
|
begin
|
|
DestroyCaret;
|
|
SetView(AView);
|
|
|
|
FVisible := false;
|
|
FHideCount := 0;
|
|
|
|
FWidth := Width;
|
|
FHeight := Height;
|
|
if Bitmap > 1 then
|
|
FBitmap := TCocoaBitmap.Create(TCocoaBitmap(Bitmap))
|
|
else
|
|
FBitmap := nil;
|
|
|
|
Result := IsValid;
|
|
end;
|
|
|
|
function TEmulatedCaret.DestroyCaret: Boolean;
|
|
begin
|
|
if Assigned(FView) then
|
|
begin
|
|
InvalidateView;
|
|
if Assigned(FView.lclGetCallback) then
|
|
FView.lclGetCallback.SetHasCaret(false);
|
|
end;
|
|
|
|
CocoaDisableTimer(FTimer);
|
|
|
|
FreeAndNil(FBitmap);
|
|
FView := nil;
|
|
FWidth := 0;
|
|
FHeight := 0;
|
|
Result := not IsValid;
|
|
end;
|
|
|
|
procedure TEmulatedCaret.DrawCaret;
|
|
begin
|
|
//DebugLn('DrawCaret ' + DbgSName(FView.LCLObject) + ' ' + DbgS(FPos) + ' ' + DbgS(FVisible) + ' ' + DbgS(FVisibleState));
|
|
//writeln('draw ', FHideCount);
|
|
if IsValid and FVisible and FVisibleState and FView.lclIsPainting and (FHideCount = 0) then
|
|
begin
|
|
if FBitmap = nil then
|
|
FView.lclGetCallback.GetContext.InvertRectangle(FPos.X, FPos.Y,
|
|
FPos.X + FWidth, FPos.Y + FHeight)
|
|
else
|
|
FView.lclGetCallback.GetContext.DrawBitmap(FPos.X, FPos.Y,
|
|
FBitmap);
|
|
end;
|
|
end;
|
|
|
|
function TEmulatedCaret.Show: Boolean;
|
|
begin
|
|
//writeln('car: ', (AView = FView),' ',(IsValid),' ',Assigned(FView));
|
|
Result := (IsValid) and Assigned(FView);
|
|
if not Result then Exit;
|
|
|
|
if (FHideCount > 0) then dec(FHideCount);
|
|
//DebugLn('ShowCaret ' + DbgSName(AView.LCLObject));
|
|
|
|
if not FVisible then
|
|
begin
|
|
// was not previously visible
|
|
InvalidateView;
|
|
FVisible := True;
|
|
FVisibleState := true;
|
|
end;
|
|
|
|
if not Assigned(FTimer) then ResetTimer;
|
|
end;
|
|
|
|
function TEmulatedCaret.Hide: Boolean;
|
|
begin
|
|
Result := IsValid;
|
|
|
|
// inside of paint, there's no need to stop timer and invalidate the drawing
|
|
inc(FHideCount);
|
|
if Assigned(FView) and (FView.lclIsPainting) then Exit;
|
|
|
|
if FVisible then
|
|
begin
|
|
CocoaDisableTimer(FTimer);
|
|
FVisible := False;
|
|
InvalidateView;
|
|
end;
|
|
end;
|
|
|
|
procedure TEmulatedCaret.SetPos(const Value: TPoint);
|
|
begin
|
|
//DebugLn('SetCaretPos ' + DbgSName(FView.LCLObject));
|
|
if FView = nil then
|
|
begin
|
|
FPos.X := 0;
|
|
FPos.Y := 0;
|
|
Exit;
|
|
end;
|
|
|
|
if ((FPos.x <> Value.x) or (FPos.y <> Value.y)) then
|
|
begin
|
|
FPos := Value;
|
|
// the caret must remain visible while changing position
|
|
FVisibleState := True;
|
|
ResetTimer;
|
|
if not FView.lclIsPainting then InvalidateView;
|
|
end;
|
|
end;
|
|
|
|
procedure TEmulatedCaret.DoTimer(Sender: TObject);
|
|
begin
|
|
FVisibleState := not FVisibleState;
|
|
if FVisible then InvalidateView;
|
|
end;
|
|
|
|
function TEmulatedCaret.IsValid: Boolean;
|
|
begin
|
|
Result := (FWidth > 0) and (FHeight > 0) and (FView <> nil) and FView.lclIsVisible
|
|
and Assigned(FView.lclGetTarget);
|
|
end;
|
|
|
|
procedure TEmulatedCaret.SetView(AView: NSView);
|
|
begin
|
|
FView := AView;
|
|
if FView <> nil then FView.lclGetCallback.HasCaret := True;
|
|
CocoaDisableTimer(FTimer);
|
|
if Assigned(FView) then
|
|
FTimer:=CocoaEnableTimer(FTimerTarget);
|
|
end;
|
|
|
|
procedure TEmulatedCaret.InvalidateView;
|
|
var
|
|
R: TRect;
|
|
begin
|
|
if (FView = nil) or FWidgetSetReleased then Exit;
|
|
if FView.lclIsPainting then Exit;
|
|
if not IsValid then Exit;
|
|
|
|
//DebugLn('UpdateCaret ' + DbgSName(FView.LCLObject) + ' ' + DbgS(FPos) + ' ' + DbgS(FVisible) + ' ' + DbgS(FVisibleState));
|
|
R.Left := FPos.x;
|
|
R.Top := FPos.y;
|
|
R.Right := R.Left + FWidth + 2;
|
|
R.Bottom := R.Top + FHeight + 2;
|
|
|
|
if not EqualRect(FOldRect, R) then FView.lclInvalidateRect(FOldRect);
|
|
FView.lclInvalidateRect(R);
|
|
|
|
FOldRect := R;
|
|
end;
|
|
|
|
procedure TEmulatedCaret.ResetTimer;
|
|
begin
|
|
CocoaDisableTimer(FTimer);
|
|
FTimer:=CocoaEnableTimer(FTimerTarget);
|
|
end;
|
|
|
|
finalization
|
|
DestroyGlobalCaret;
|
|
|
|
end.
|