mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-08-15 03:39:09 +02:00
Cocoa: better handling of TCocoaWindow.makeFirstResponder() reentrancy
This commit is contained in:
parent
957d62af56
commit
185e72ea31
@ -119,6 +119,9 @@ type
|
|||||||
TCocoaWindowContent = objcclass;
|
TCocoaWindowContent = objcclass;
|
||||||
|
|
||||||
TCocoaWindow = objcclass(NSWindow, NSWindowDelegateProtocol)
|
TCocoaWindow = objcclass(NSWindow, NSWindowDelegateProtocol)
|
||||||
|
private
|
||||||
|
// for the reentrancy of makeFirstResponder()
|
||||||
|
makeFirstResponderCount: Integer;
|
||||||
protected
|
protected
|
||||||
fieldEditor: TCocoaFieldEditor;
|
fieldEditor: TCocoaFieldEditor;
|
||||||
firedMouseEvent: Boolean;
|
firedMouseEvent: Boolean;
|
||||||
@ -1003,28 +1006,36 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// return proper focused responder by kind of class of NSResponder
|
// return proper focused responder by kind of class of NSResponder
|
||||||
function getProperFocusedResponder( const aResponder : NSResponder): NSResponder;
|
function getProperFocusedResponder( const aResponder : NSResponder ): NSResponder;
|
||||||
var
|
|
||||||
dl : NSObject;
|
|
||||||
begin
|
begin
|
||||||
Result := aResponder;
|
Result := aResponder;
|
||||||
if Result.isKindOfClass(TCocoaFieldEditor) then
|
|
||||||
begin
|
|
||||||
dl := {%H-}NSObject( TCocoaFieldEditor(Result).delegate );
|
|
||||||
if Assigned(dl) and (dl.isKindOfClass(NSView)) then
|
|
||||||
Result := NSResponder(dl);
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if Result.isKindOfClass(NSWindow) then
|
if Result.isKindOfClass(NSWindow) then
|
||||||
|
Result:= TCocoaWindowContent(NSWindow(Result).contentView).documentView;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// return responder callback by kind of class of NSResponder
|
||||||
|
function getResponderCallback( const aResponder : NSResponder ): ICommonCallback;
|
||||||
|
var
|
||||||
|
newResponder: NSResponder;
|
||||||
|
dl : NSObject;
|
||||||
|
begin
|
||||||
|
Result:= nil;
|
||||||
|
if not Assigned(aResponder) then exit;
|
||||||
|
|
||||||
|
newResponder := aResponder;
|
||||||
|
if newResponder.isKindOfClass(NSText) then
|
||||||
begin
|
begin
|
||||||
// critical step to avoid infinite loops caused by
|
dl := {%H-}NSObject( NSText(newResponder).delegate );
|
||||||
// 'Focus-fight' between LCL and COCOA
|
if Assigned(dl) and (dl.isKindOfClass(NSView)) then
|
||||||
Result := nil;
|
newResponder := NSResponder(dl);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
Result := Result.lclContentView;
|
newResponder := newResponder.lclContentView;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
if Assigned(newResponder) then
|
||||||
|
Result:= newResponder.lclGetCallback;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// send KillFocus/SetFocus messages to LCL at the right time
|
// send KillFocus/SetFocus messages to LCL at the right time
|
||||||
@ -1037,31 +1048,44 @@ end;
|
|||||||
// 3. makeFirstResponder() already avoids infinite loops caused by 'Focus-fight'
|
// 3. makeFirstResponder() already avoids infinite loops caused by 'Focus-fight'
|
||||||
// between LCL and COCOA, see also:
|
// between LCL and COCOA, see also:
|
||||||
// https://wiki.lazarus.freepascal.org/Cocoa_Internals/Application#Focus_Change
|
// https://wiki.lazarus.freepascal.org/Cocoa_Internals/Application#Focus_Change
|
||||||
|
// 4. makeFirstResponder() is Reentrant and Thread-safe
|
||||||
|
//
|
||||||
function TCocoaWindow.makeFirstResponder( aResponder : NSResponder ): ObjCBOOL;
|
function TCocoaWindow.makeFirstResponder( aResponder : NSResponder ): ObjCBOOL;
|
||||||
var
|
var
|
||||||
lastResponder : NSResponder;
|
lastResponder : NSResponder;
|
||||||
newResponder : NSResponder;
|
newResponder : NSResponder;
|
||||||
|
cb : ICommonCallback;
|
||||||
begin
|
begin
|
||||||
lastResponder := self.firstResponder;
|
inc( makeFirstResponderCount );
|
||||||
newResponder := aResponder;
|
try
|
||||||
|
lastResponder := self.firstResponder;
|
||||||
|
newResponder := getProperFocusedResponder( aResponder );
|
||||||
|
if lastResponder = newResponder then exit;
|
||||||
|
|
||||||
// do toggle Focused Control
|
// do toggle Focused Control
|
||||||
// Result=false when the focused control has not been changed
|
// Result=false when the focused control has not been changed
|
||||||
Result := inherited makeFirstResponder( newResponder );
|
// TCocoaWindow.makeFirstResponder() may be triggered reentrant here
|
||||||
if not Result then exit;
|
Result := inherited makeFirstResponder( newResponder );
|
||||||
|
if not Result then exit;
|
||||||
|
|
||||||
// send KillFocus/SetFocus messages to LCL
|
// send KillFocus/SetFocus messages to LCL only at level one
|
||||||
// 1st: send KillFocus Message first
|
if makeFirstResponderCount > 1 then
|
||||||
lastResponder := getProperFocusedResponder( lastResponder );
|
exit;
|
||||||
if Assigned(lastResponder) and Assigned(lastResponder.lclGetCallback) then
|
|
||||||
lastResponder.lclGetCallback.ResignFirstResponder;
|
|
||||||
|
|
||||||
// 2st: send SetFocus Message
|
// 1st: send KillFocus Message first
|
||||||
// focused control may not be aResponder
|
cb:= getResponderCallback( lastResponder );
|
||||||
// get focused control via firstResponder again
|
if Assigned(cb) then
|
||||||
newResponder := getProperFocusedResponder( self.firstResponder );
|
cb.ResignFirstResponder;
|
||||||
if Assigned(newResponder) and Assigned(newResponder.lclGetCallback) then
|
|
||||||
newResponder.lclGetCallback.BecomeFirstResponder;
|
// 2st: send SetFocus Message
|
||||||
|
// TCocoaWindow.makeFirstResponder() may be triggered reentrant here
|
||||||
|
cb := getResponderCallback( self.firstResponder );
|
||||||
|
if Assigned(cb) then
|
||||||
|
cb.BecomeFirstResponder;
|
||||||
|
|
||||||
|
finally
|
||||||
|
dec( makeFirstResponderCount );
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user