View Issue Details

IDProjectCategoryView StatusLast Update
0021752LazarusLCLpublic2020-05-06 22:08
ReporterDavid Jenkins Assigned To 
PrioritynormalSeverityfeatureReproducibilityN/A
Status newResolutionopen 
Product Version0.9.30.5 (SVN) 
Summary0021752: macOS "Full Keyboard Access" disabled enhancements
DescriptionFix tabbing between controls when "Full Keyboard Access" is disabled (only tab to edits and listboxes)

Patch against rev 36681 attached
TagsNo tags attached.
Fixed in Revision
LazTarget-
WidgetsetCarbon, Cocoa
Attached Files

Activities

2012-04-13 21:22

 

wincontrol.inc.patch (578 bytes)   
--- /Users/djenkins/laz-changes/14695/wincontrol.inc	2012-04-11 14:41:26.000000000 
+++ /Users/djenkins/laz-changes/14695/wincontrol.inc.ss	2012-04-11 14:42:18.000000000 
@@ -4498,7 +4498,7 @@
           +' TestEnVi='+dbgs(Next.Enabled and Next.IsVisible)]);}
         if (((not CheckTabStop) or Next.TabStop)
         and ((not CheckParent) or (Next.Parent = Self)))
-        and (Next.Enabled and Next.IsVisible) then
+        and Next.IsVisible and Next.CanTab then
           Result := Next;
 
         // if we reached the start then exit because we traversed the loop and
wincontrol.inc.patch (578 bytes)   

2012-04-19 17:48

 

svn_patch-21752 (603 bytes)   
Index: lcl/include/wincontrol.inc
===================================================================
--- lcl/include/wincontrol.inc	(revision 36681)
+++ lcl/include/wincontrol.inc	(working copy)
@@ -4498,7 +4498,7 @@
           +' TestEnVi='+dbgs(Next.Enabled and Next.IsVisible)]);}
         if (((not CheckTabStop) or Next.TabStop)
         and ((not CheckParent) or (Next.Parent = Self)))
-        and (Next.Enabled and Next.IsVisible) then
+        and Next.IsVisible and Next.CanTab then
           Result := Next;
 
         // if we reached the start then exit because we traversed the loop and
svn_patch-21752 (603 bytes)   

CudaText man

2017-04-09 23:34

reporter   ~0099533

@Ondrej or @Juha
It's LCL patch

This pch makes code slower: CanTab is much slower than Enabled. (It sees all parents of control, it calls WS).
So decide, is it Ok.

Juha Manninen

2017-04-10 11:26

developer   ~0099540

The code is in TWinControl.FindNextControl.
Is there any way to solve it in widgetset code?
I will leave this to somebody who knows Mac and Carbon.

Zoë Peterson

2020-03-05 23:49

reporter   ~0121403

Last edited: 2020-03-05 23:57

View 2 revisions

This patch builds on David's patch above to also make the behavior correct on Cocoa.

For system controls like NSButton the canBecomeKeyView accessor already switches based on NSApp.isFullKeyboardAccessEnabled. For custom controls, the new FollowFullKeyboardAccess field in TLCLCommonCallback allows them to use the same behavior. It isn't used in any stock LCL controls since the higher level LCL classes don't have the concept, so we currently turn it on in our CreateWnd. Classes like TCustomCheckBoxThemed would benefit from it though. Possibly expose it as a new TControlStyle or property on TCustomControl?

As for Juha's question, I don't think it's possible to make this work solely on the widgetset layer. PerformTab and SelectNext both just call FindNextControl and then Control.SetFocus. There isn't anywhere besides CanTab for the widgetset to insert any behavior, and in the case of Cocoa, the widgetset has to ask the underlying handle because the system toggles the state in the background and doesn't provide a notice if it changes. I'd love to be proven wrong, but short of allowing the widgetset to completely replace CanTab/FindNextControl, I don't see how to do it.

cocoa-full-keyboard-access.patch (2,063 bytes)   
diff --git a/lcl/interfaces/cocoa/cocoawscommon.pas b/lcl/interfaces/cocoa/cocoawscommon.pas
index 2d989748da..a5b501cc48 100644
--- a/lcl/interfaces/cocoa/cocoawscommon.pas
+++ b/lcl/interfaces/cocoa/cocoawscommon.pas
@@ -62,6 +62,7 @@ type
     BlockCocoaKeyBeep: Boolean;
     SuppressTabDown: Boolean; // all tabs should be suppressed, so Cocoa would not switch focus
     ForceReturnKeyDown: Boolean; // send keyDown/LM_KEYDOWN for Return even if handled by IntfUTF8KeyPress/CN_CHAR
+    FollowFullKeyboardAccess: Boolean;
 
     class constructor Create;
     constructor Create(AOwner: NSObject; ATarget: TWinControl; AHandleFrame: NSView = nil); virtual;
@@ -121,6 +122,7 @@ type
 
   TCocoaWSWinControl = class(TWSWinControl)
   published
+    class function CanFocus(const AWinControl: TWinControl): Boolean; override;
     class function CreateHandle(const AWinControl: TWinControl;
       const AParams: TCreateParams): TLCLIntfHandle; override;
     class procedure DestroyHandle(const AWinControl: TWinControl); override;
@@ -886,7 +888,8 @@ end;
 
 function TLCLCommonCallback.CanFocus: Boolean;
 begin
-  Result := not Assigned(Target) or not (csDesigning in Target.ComponentState);
+  Result := (not Assigned(Target) or not (csDesigning in Target.ComponentState)) and
+    (not FollowFullKeyboardAccess or NSApp.isFullKeyboardAccessEnabled);
 end;
 
 procedure TLCLCommonCallback.MouseClick;
@@ -1519,6 +1522,21 @@ end;
 
 { TCocoaWSWinControl }
 
+class function TCocoaWSWinControl.CanFocus(const AWinControl: TWinControl): Boolean;
+var
+  obj: NSObject;
+begin
+  Result := inherited CanFocus(AWinControl);
+  if not Result then
+    Exit;
+  if AWinControl.HandleAllocated then
+  begin
+    obj := NSObject(TWinControl(AWinControl).Handle);
+    if obj.isKindOfClass_(NSView) then
+      Result := NSViewCanFocus(NSView(obj)) and NSView(obj).canBecomeKeyView;
+  end;
+end;
+
 class function TCocoaWSWinControl.CreateHandle(const AWinControl: TWinControl;
   const AParams: TCreateParams): TLCLIntfHandle;
 begin
-- 
2.20.1 (Apple Git-117)

Zoë Peterson

2020-03-05 23:55

reporter   ~0121404

Since it's not clear in my last message, both David's and my patch are necessary for correction functionality on Cocoa

Dmitry Boyarintsev

2020-03-06 05:58

developer   ~0121408

unfortunately "CanTab" cannot be used the way provide in the patch.
"CanTab" method is used to initiate "TabStop" property at TWinControl.Insert method, for the designer. (it seems to be the only use in the current system).

That means, that any controls designed in macOS without full navigation, will have TabStop default "false", instead of "true" on any other system.

The intent of TWSWinControl.CanFocus() is also unclear.
The common WS implementation (and Win32 non-implementation) suggests that CanFocus() should return a flag if the widgetset is "potentially" focusable (no matter, if it's enabled and/or visible)

Qt4 implementation does actually verification if the control is enabled and visible. (which is contradicts Win32 non-implementation)

Qt5 is actually based on "focusPolicy" property, that return the possibility of a widget being focused (which makes sense).
https://doc.qt.io/qt-5/qwidget.html#focusPolicy-prop
-----
Suggesting the following changes.
1) since TWSWinControl.CanFocus() currently works differently on each widgeset, remove the use CanTab() from TWinControl.Insert (always setting TabStop to true)
1.1) OPTIONAL: change default of "TabStop" property at TWinControl from "false" to "true" and never change it at TWinControl.Insert() leaving the LCL control to decide if it can tabStop or not by default.

2) declare the role of TWSWinControl.CanFocus() is to return if the widget can be focused (w/o checking it's visibility and enabled state. Which is handled by LCL level anyway).

3) use CanTab during the FindNextControl() as suggested in the initial patch

CudaText man

2020-03-06 11:55

reporter   ~0121412

This sounds logical.

Dmitry Boyarintsev

2020-03-08 23:56

developer   ~0121477

started a feature branch:
https://svn.freepascal.org/svn/lazarus/branches/macosfullkeyboardaccess

David Jenkins

2020-05-06 22:08

reporter   ~0122649

Dmitry,

We are having a problem with TToolbar trying to take focus. This is related to this discussion so I wanted to post here what we are currently doing to deal with TToolbar.

TToolbar.CanFocus returns false (always) and TToolbar.Handle is TCocoaCustomControl. So

a) I added a check for Target.CanFocus to TLCLCommonCallback.CanFocus
b) Changed TCocoaCustomControl.acceptsFirstResponder to return Callback.CanFocus.

Since you have started a branch for this if this doesn't fit well with what you are planning then I'd be happy to switch to something to does fit (if there is an easy way to do it without the LCL layer changes).

Also, I noticed while looking through what the other interfaces did with TToolbar that LCL has a .ControlStyle called 'csNoFocus'. THe documentation says, “control will not take focus when clicked with mouse". I didn't see this mentioned in your documentation above and wanted to point it out as it might be the method intended to differentiate between tab focus and click focus (like QT does).

Issue History

Date Modified Username Field Change
2012-04-13 21:22 David Jenkins New Issue
2012-04-13 21:22 David Jenkins File Added: wincontrol.inc.patch
2012-04-13 21:22 David Jenkins Widgetset => Carbon
2012-04-19 17:48 David Jenkins File Added: svn_patch-21752
2017-04-09 23:34 CudaText man Note Added: 0099533
2017-04-10 11:26 Juha Manninen Note Added: 0099540
2020-03-05 23:49 Zoë Peterson File Added: cocoa-full-keyboard-access.patch
2020-03-05 23:49 Zoë Peterson Note Added: 0121403
2020-03-05 23:55 Zoë Peterson Note Added: 0121404
2020-03-05 23:57 Zoë Peterson Note Edited: 0121403 View Revisions
2020-03-06 05:38 Dmitry Boyarintsev LazTarget => -
2020-03-06 05:38 Dmitry Boyarintsev Widgetset Carbon => Carbon, Cocoa
2020-03-06 05:58 Dmitry Boyarintsev Note Added: 0121408
2020-03-06 11:55 CudaText man Note Added: 0121412
2020-03-06 14:48 Dmitry Boyarintsev Summary Carbon "Full Keyboard Access" disabled enhancements => macOS "Full Keyboard Access" disabled enhancements
2020-03-06 14:48 Dmitry Boyarintsev Widgetset Carbon, Cocoa => Carbon, Cocoa
2020-03-08 23:56 Dmitry Boyarintsev Note Added: 0121477
2020-05-06 22:08 David Jenkins Note Added: 0122649