@@ -49,9 +49,6 @@ def queueEvent(eventName,obj,**kwargs):
4949 @param eventName: the name of the event type (e.g. 'gainFocus', 'nameChange')
5050 @type eventName: string
5151 """
52- # Allow NVDAObjects to redirect focus events to another object of their choosing.
53- if eventName == "gainFocus" and obj .focusRedirect :
54- obj = obj .focusRedirect
5552 _trackFocusObject (eventName , obj )
5653 with _pendingEventCountsLock :
5754 _pendingEventCountsByName [eventName ]= _pendingEventCountsByName .get (eventName ,0 )+ 1
@@ -242,7 +239,7 @@ def isForegroundObject(self):
242239 def isMenuItemOfCurrentFocus (self ) -> bool :
243240 """
244241 Checks if the current object is a menu item of the current focus.
245- The only known case where this returns True is the following (see #12624):
242+ The only known case where this returns True is the following (see #12624, #14550 ):
246243
247244 When opening a submenu in certain applications (like Thunderbird 78.12),
248245 NVDA can process a menu start event after the first item in the menu is focused.
@@ -253,24 +250,39 @@ def isMenuItemOfCurrentFocus(self) -> bool:
253250 The focus event order after activating the menu item's sub menu is (submenu item, submenu).
254251 """
255252 from NVDAObjects import IAccessible
253+ from comInterfaces import IAccessible2Lib as IA2
254+
256255 lastFocus = api .getFocusObject ()
257- _isMenuItemOfCurrentFocus = (
258- self ._obj .parent
259- and isinstance (self ._obj , IAccessible .IAccessible )
260- and isinstance (lastFocus , IAccessible .IAccessible )
261- and self ._obj .IAccessibleRole == oleacc .ROLE_SYSTEM_MENUITEM
262- and lastFocus .IAccessibleRole == oleacc .ROLE_SYSTEM_MENUPOPUP
263- and self ._obj .parent == lastFocus
264- )
265- if _isMenuItemOfCurrentFocus :
266- # Change this to log.error for easy debugging
267- log .debugWarning (
268- "This parent menu was not announced properly, "
269- "and should have been focused before the submenu item.\n "
270- f"Object info: { self ._obj .devInfo } \n "
271- f"Object parent info: { self ._obj .parent .devInfo } \n "
272- )
273- return _isMenuItemOfCurrentFocus
256+
257+ # This case can only occur when:
258+ # 1. the old and new focus targets are instances of IAccessible; and
259+ # 2. the old focus is a menuitem, menuitemradio or menuitemcheckbox; and
260+ # 3. the new focus is a menu; and
261+ # 4. the old focus has a parent.
262+ if not (
263+ isinstance (self ._obj , IAccessible .IAccessible )
264+ and isinstance (lastFocus , IAccessible .IAccessible )
265+ and self ._obj .IAccessibleRole in (
266+ oleacc .ROLE_SYSTEM_MENUITEM , IA2 .IA2_ROLE_CHECK_MENU_ITEM , IA2 .IA2_ROLE_RADIO_MENU_ITEM )
267+ and lastFocus .IAccessibleRole == oleacc .ROLE_SYSTEM_MENUPOPUP
268+ and self ._obj .parent ):
269+ return False
270+
271+ # Check that the old focus is a descendant of the new focus.
272+ ancestor = self ._obj .parent
273+ while ancestor is not None :
274+ if ancestor == lastFocus :
275+ log .debugWarning (
276+ "This ancestor menu was not announced properly, and should have been focused before the submenu item.\n "
277+ "Object info: %s\n "
278+ f"Ancestor info: %s\n " ,
279+ self ._obj .devInfo , ancestor .devInfo
280+ )
281+ return True
282+
283+ ancestor = ancestor .parent
284+
285+ return False
274286
275287
276288def _getFocusLossCancellableSpeechCommand (
@@ -306,6 +318,9 @@ def executeEvent(
306318 try :
307319 global _virtualDesktopName
308320 isGainFocus = eventName == "gainFocus"
321+ # Allow NVDAObjects to redirect focus events to another object of their choosing.
322+ if isGainFocus and obj .focusRedirect :
323+ obj = obj .focusRedirect
309324 sleepMode = obj .sleepMode
310325 # Handle possible virtual desktop name change event.
311326 # More effective in Windows 10 Version 1903 and later.
0 commit comments