Repository: nvaccess/nvda

Issue:
JAB: nvda should not treat `ActiveDescendentChange` as a `focus gained` event
See code at https://github.com/nvaccess/nvda/blob/1aae7484bd84b2f5757e8ee65911db9fd27b56fc/source/JABHandler.py#L567

When a `PropertyActiveDescendentChange` event is received, nvda transform it into a `event_gainFocus` event.

The issue is that `PropertyActiveDescendentChange` can be raised by components (e.g. tree view) that don't have the focus. In that case, a `PropertyActiveDescendentChange` does not imply that the newly selected tree node has received the focus.

Take for example IntelliJ/Android Studio. When the user typed code in the text editor for a source file, for example a Java source file, IntelliJ/Android Studio asynchronously updates a tree contained in the "Stucture" toolwindow. The "Structure" toolwindow essentially shows the list of methods/nested classes, etc. of the currently edited source file. One of the feature of the "Structure" toolwindow is that it makes the tree node corresponding to the current java method active in the tree view, so when the caret moves from one method to another, the active node is updated (asynchronously). Node that the editor keeps the focus the whole time, and the tree itself never receives the focus.

In this example, the problem with the current implementation of the `PropertyActiveDescendentChange` event handler in the example above is that nvda assumes the editor loses the focus when the event is received. From then one, nvda will then ignore caret events because it thinks the component with the focus is the tree view.

I think a reasonable fix would be to ignore `PropertyActiveDescendentChange` events unless the component sending the event (e.g. the tree) has the focus.



Discussion:
We didn't realise PropertyActiveDescendentChange could be fired by 
something other than the focus. I'm happy with the proposed fix.

@rpaquay, were you planning to submit a PR for this? (I realise there are several outstanding PRs from you already, but just wanted to check you weren't being blocked by something if you were planning to handle this.)

Yes, but it is somewhat lower priority for me, as it is a not a blocking issue -- I implemented a workaround in Android Studio so that most components don't send the event if they don't have the input focus. I should still work on a patch for nvda, since my workaround does not cover 100% of cases.

I think I have the same problem as reported above.
Our Java Swing-based application has side views and whenever I move the cursor in the text content the "Outline" side view selects the appropriate node structure in its internal tree. We do that without giving explicit focus to the Outline view, but NVDA still reads the changes made in the Outline view whenever I move the cursor in the main text area.
I would really appreciate a fix in NVDA for this, our client enjoys NVDA better than JAWS but right now they need to stick to using JAWS because of this problem.

I also reproduced the problem with a small Java application:

        public static void main(String[] args) {
         JFrame fr = new JFrame("AAA");
         fr.setSize(500, 500);
         JPanel panel = new JPanel(new GridBagLayout());
         GridBagConstraints c = new GridBagConstraints();
         c.gridy = 0;
         c.gridx = 0;
         JTree tree = new JTree();
         JTextArea jt = new JTextArea("SOME\na\nb\nctext");
         jt.addCaretListener(new CaretListener() {
         @Override
         public void caretUpdate(CaretEvent e) {
         tree.setSelectionRow(e.getDot() % 4);
         }
         });
         panel.add(jt, c);
         c.gridy++;
         panel.add(tree, c);
         fr.getContentPane().add(panel, BorderLayout.CENTER);
         fr.setVisible(true);
        } 
@rpaquay it would be great if you would open a pull request for this, I could try to build the project on my side and see if your fix also helps my problem.
I managed to custom compile NVDA but as I do not know Python and the NVDA code I'm struggling coming up with a fix for this. Can you give me a suggestion about how the "internal_event_activeDescendantChange" method could be changed in order to avoid it firing a focus gained event if the component does not actually hold the focus?
I tried something like this but it does not properly work:

            def internal_event_activeDescendantChange(vmID, event,source,oldDescendant,newDescendant):
            	jabContext=JABContext(vmID=vmID,accContext=source)
            	focus=api.getFocusObject()
            	if isinstance(focus, NVDAObjects.JAB.JAB) and focus.jabContext == jabContext:
            		hwnd=getWindowHandleFromAccContext(vmID,source)
            		internalQueueFunction(event_gainFocus,vmID,newDescendant,hwnd)
            		for accContext in [event,oldDescendant]:
            			bridgeDll.releaseJavaObject(vmID,accContext)
@feerrenrut could you please have a look and see what still needs to be done for this one?

Resolving patch:
diff --git a/source/JABHandler.py b/source/JABHandler.py
--- a/source/JABHandler.py
+++ b/source/JABHandler.py
@@ -630,11 +630,25 @@ def event_gainFocus(vmID,accContext,hwnd):
 	eventHandler.queueEvent("gainFocus",obj)
 
 @AccessBridge_PropertyActiveDescendentChangeFP
-def internal_event_activeDescendantChange(vmID, event,source,oldDescendant,newDescendant):
-	hwnd=getWindowHandleFromAccContext(vmID,source)
-	internalQueueFunction(event_gainFocus,vmID,newDescendant,hwnd)
-	for accContext in [event,oldDescendant]:
-		bridgeDll.releaseJavaObject(vmID,accContext)
+def internal_event_activeDescendantChange(vmID, event, source, oldDescendant, newDescendant):
+	hwnd = getWindowHandleFromAccContext(vmID, source)
+	sourceContext = JABContext(hwnd=hwnd, vmID=vmID, accContext=source)
+	if internal_hasFocus(sourceContext):
+		internalQueueFunction(event_gainFocus, vmID, newDescendant, hwnd)
+	for accContext in [event, oldDescendant]:
+		bridgeDll.releaseJavaObject(vmID, accContext)
+
+
+def internal_hasFocus(sourceContext):
+	focus = api.getFocusObject()
+	if isinstance(focus, NVDAObjects.JAB.JAB) and focus.jabContext == sourceContext:
+		return True
+	ancestors = api.getFocusAncestors()
+	for ancestor in reversed(ancestors):
+		if isinstance(ancestor, NVDAObjects.JAB.JAB) and ancestor.jabContext == sourceContext:
+			return True
+	return False
+
 
 @AccessBridge_PropertyNameChangeFP
 def event_nameChange(vmID,event,source,oldVal,newVal):


Test patch: