File: cocoacaret.pas

package info (click to toggle)
lazarus 2.0.10%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 219,188 kB
  • sloc: pascal: 1,867,962; xml: 265,716; cpp: 56,595; sh: 3,005; java: 609; makefile: 568; perl: 297; sql: 222; ansic: 137
file content (389 lines) | stat: -rw-r--r-- 8,887 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
{
 /***************************************************************************
                    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.