mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-09-22 16:09:38 +02:00
FIX: COCOA: send KillFocus/SetFocus messages to LCL at the right time, adapt to LCL, just like Win32 (by TCocoaWindow.makeFirstResponder() issues with infinite loops fixed)
This commit is contained in:
parent
467026508f
commit
f4acf204fb
@ -519,7 +519,6 @@ var
|
||||
allowcocoa : Boolean;
|
||||
idx: integer;
|
||||
win : NSWindow;
|
||||
cbnew : ICommonCallback;
|
||||
responder : NSResponder;
|
||||
begin
|
||||
{$ifdef COCOALOOPNATIVE}
|
||||
@ -601,25 +600,8 @@ begin
|
||||
|
||||
finally
|
||||
|
||||
// Focus change notification used to be in makeFirstResponder method
|
||||
// However, it caused many issues with infinite loops.
|
||||
// Sometimes Cocoa like to switch focus to window (temporary) (i.e. when switching tabs)
|
||||
// That's causing a conflict with LCL. LCL tries to switch focus back
|
||||
// to the original control. And Cocoa keep switching it back to the Window.
|
||||
// (Note, that for Cocoa, window should ALWAYS be focusable)
|
||||
// Thus, Focus switching notification was moved to post event handling.
|
||||
//
|
||||
// can't have this code in TCocoaWindow, because some key events are not forwarded
|
||||
// to the window
|
||||
cbnew := win.firstResponder.lclGetCallback;
|
||||
if not isCallbackForSameObject(cb, cbnew) then
|
||||
begin
|
||||
if Assigned(cb) then cb.ResignFirstResponder;
|
||||
cbnew := win.firstResponder.lclGetCallback;
|
||||
if Assigned(cbnew) then cbnew.BecomeFirstResponder;
|
||||
end;
|
||||
|
||||
CocoaWidgetSet.ReleaseToCollect(idx);
|
||||
|
||||
end;
|
||||
{$ifdef COCOALOOPNATIVE}
|
||||
if CocoaWidgetSet.FTerminating then stop(nil);
|
||||
|
@ -2535,12 +2535,6 @@ begin
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function NeedsFocusNotifcation(event: NSEvent; win: NSWindow): Boolean;
|
||||
begin
|
||||
Result := (Assigned(win))
|
||||
and (not Assigned(event) or (event.window <> win));
|
||||
end;
|
||||
|
||||
function TCocoaWidgetSet.SetFocus(Handle: HWND): HWND;
|
||||
var
|
||||
Obj: NSObject;
|
||||
@ -2566,15 +2560,7 @@ begin
|
||||
if lView.window <> nil then
|
||||
begin
|
||||
lView.window.makeKeyWindow;
|
||||
if lView.window.makeFirstResponder(lView.lclContentView) then
|
||||
begin
|
||||
// initial focus set (right before the event loop starts)
|
||||
if NeedsFocusNotifcation(NSApp.currentEvent, lView.window) then
|
||||
begin
|
||||
cb := lView.lclGetCallback;
|
||||
if Assigned(cb) then cb.BecomeFirstResponder;
|
||||
end;
|
||||
end;
|
||||
lView.window.makeFirstResponder(lView.lclContentView);
|
||||
end else
|
||||
Result := 0; // the view is on window, cannot set focus. Fail
|
||||
end else
|
||||
|
@ -145,10 +145,8 @@ type
|
||||
keepWinLevel : NSInteger;
|
||||
//LCLForm: TCustomForm;
|
||||
procedure dealloc; override;
|
||||
function acceptsFirstResponder: LCLObjCBoolean; override;
|
||||
function makeFirstResponder(aResponder: NSResponder): ObjCBOOL; override;
|
||||
function canBecomeKeyWindow: LCLObjCBoolean; override;
|
||||
function becomeFirstResponder: LCLObjCBoolean; override;
|
||||
function resignFirstResponder: LCLObjCBoolean; override;
|
||||
function lclGetCallback: ICommonCallback; override;
|
||||
procedure lclClearCallback; override;
|
||||
// mouse
|
||||
@ -737,6 +735,7 @@ begin
|
||||
//DebugLn('[TCocoaWindow.windowWillReturnFieldEditor_toObject]');
|
||||
Result := nil;
|
||||
|
||||
// NSTextView itself is NSTextFieldEditor, then windowWillReturnFieldEditor never called for NSTextView
|
||||
if (NSObject(client).isKindOfClass(NSTextField)) and Assigned(NSObject(client).lclGetCallBack) then
|
||||
begin
|
||||
if (fieldEditor = nil) then
|
||||
@ -842,32 +841,11 @@ begin
|
||||
inherited dealloc;
|
||||
end;
|
||||
|
||||
function TCocoaWindow.acceptsFirstResponder: LCLObjCBoolean;
|
||||
begin
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function TCocoaWindow.canBecomeKeyWindow: LCLObjCBoolean;
|
||||
begin
|
||||
Result := Assigned(callback) and callback.CanActivate;
|
||||
end;
|
||||
|
||||
function TCocoaWindow.becomeFirstResponder: LCLObjCBoolean;
|
||||
begin
|
||||
Result := inherited becomeFirstResponder;
|
||||
// uncommenting the following lines starts an endless focus loop
|
||||
|
||||
// if Assigned(callback) then
|
||||
// callback.BecomeFirstResponder;
|
||||
end;
|
||||
|
||||
function TCocoaWindow.resignFirstResponder: LCLObjCBoolean;
|
||||
begin
|
||||
Result := inherited resignFirstResponder;
|
||||
// if Assigned(callback) then
|
||||
// callback.ResignFirstResponder;
|
||||
end;
|
||||
|
||||
function TCocoaWindow.lclGetCallback: ICommonCallback;
|
||||
begin
|
||||
Result := callback;
|
||||
@ -1022,6 +1000,69 @@ begin
|
||||
inherited keyDown(event);
|
||||
end;
|
||||
|
||||
// return proper focused responder by kind of class of NSResponder
|
||||
function getProperFocusedResponder( const aResponder : NSResponder): NSResponder;
|
||||
var
|
||||
dl : NSObject;
|
||||
begin
|
||||
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
|
||||
begin
|
||||
// critical step to avoid infinite loops caused by
|
||||
// 'Focus-fight' between LCL and COCOA
|
||||
Result := nil;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := Result.lclContentView;
|
||||
end;
|
||||
end;
|
||||
|
||||
// send KillFocus/SetFocus messages to LCL at the right time
|
||||
// 1. KillFocus/SetFocus messages should be sent after LCLIntf.SetFocus() in LCL,
|
||||
// and before generating CM_UIACTIVATE message in LCL,
|
||||
// this adapts to LCL, just like Win32.
|
||||
// 2. if KillFocus/SetFocus messages are delayed,
|
||||
// such as at the end of TCocoaApplication.sendevent(), it will cause many problems.
|
||||
// for example, there are two buttons showing the selected state.
|
||||
// 3. makeFirstResponder() already avoids infinite loops caused by 'Focus-fight'
|
||||
// between LCL and COCOA, see also:
|
||||
// https://wiki.lazarus.freepascal.org/Cocoa_Internals/Application#Focus_Change
|
||||
function TCocoaWindow.makeFirstResponder( aResponder : NSResponder ): ObjCBOOL;
|
||||
var
|
||||
lastResponder : NSResponder;
|
||||
newResponder : NSResponder;
|
||||
begin
|
||||
lastResponder := self.firstResponder;
|
||||
newResponder := aResponder;
|
||||
|
||||
// do toggle Focused Control
|
||||
// Result=false when the focused control has not been changed
|
||||
Result := inherited makeFirstResponder( newResponder );
|
||||
if not Result then exit;
|
||||
|
||||
// send KillFocus/SetFocus messages to LCL
|
||||
// 1st: send KillFocus Message first
|
||||
lastResponder := getProperFocusedResponder( lastResponder );
|
||||
if Assigned(lastResponder) and Assigned(lastResponder.lclGetCallback) then
|
||||
lastResponder.lclGetCallback.ResignFirstResponder;
|
||||
|
||||
// 2st: send SetFocus Message
|
||||
// focused control may not be aResponder
|
||||
// get focused control via firstResponder again
|
||||
newResponder := getProperFocusedResponder( self.firstResponder );
|
||||
if Assigned(newResponder) and Assigned(newResponder.lclGetCallback) then
|
||||
newResponder.lclGetCallback.BecomeFirstResponder;
|
||||
end;
|
||||
|
||||
|
||||
function TCocoaWindowContentDocument.draggingEntered(sender: NSDraggingInfoProtocol): NSDragOperation;
|
||||
begin
|
||||
Result := NSDragOperationNone;
|
||||
|
Loading…
Reference in New Issue
Block a user