[orca] Fix several popup menu-related issues



commit 5b14a13696098e5ed70b23042c97bb38ebe5dce0
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Fri Aug 26 13:10:20 2022 +0200

    Fix several popup menu-related issues
    
    * Ensure we present Thunderbird's New Address Book popup menu when
      it first appears (issue #261)
    * Don't present child position for menus if there is only one menu
    * Don't present the name of the menu if it's the same as the widget
      which caused the menu to pop up

 src/orca/script_utilities.py                       | 25 ++++++++++++++++++
 src/orca/scripts/apps/Thunderbird/script.py        |  6 +++++
 src/orca/scripts/toolkits/Chromium/script.py       | 30 +++++++++++++++-------
 .../scripts/toolkits/Chromium/script_utilities.py  | 21 +--------------
 src/orca/scripts/web/speech_generator.py           |  6 +++++
 src/orca/speech_generator.py                       | 10 ++++++++
 6 files changed, 69 insertions(+), 29 deletions(-)
---
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index bbdf8e54a..c603890fb 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -4226,6 +4226,31 @@ class Utilities:
 
         return role == pyatspi.ROLE_PUSH_BUTTON and state.contains(pyatspi.STATE_HAS_POPUP)
 
+    def isPopupMenuForCurrentItem(self, obj):
+        if obj == orca_state.locusOfFocus:
+            return False
+
+        if obj.name and obj.name == orca_state.locusOfFocus.name:
+            return obj.getRole() == pyatspi.ROLE_MENU
+
+        return False
+
+    def isMenuWithNoSelectedChild(self, obj):
+        if not obj:
+            return False
+
+        try:
+            role = obj.getRole()
+        except:
+            msg = "ERROR: Exception getting role for %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return False
+
+        if role != pyatspi.ROLE_MENU:
+            return False
+
+        return not self.selectedChildCount(obj)
+
     def isMenuButton(self, obj):
         if not obj:
             return False
diff --git a/src/orca/scripts/apps/Thunderbird/script.py b/src/orca/scripts/apps/Thunderbird/script.py
index a19e15c74..843e34000 100644
--- a/src/orca/scripts/apps/Thunderbird/script.py
+++ b/src/orca/scripts/apps/Thunderbird/script.py
@@ -255,6 +255,12 @@ class Script(Gecko.Script):
         # right now just to prevent the Gecko script from presenting non-
         # existent browsery autocompletes for Thunderbird.
 
+        if event.detail1 and self.utilities.isMenuWithNoSelectedChild(event.source) \
+           and orca_state.activeWindow == self.utilities.topLevelObject(event.source):
+            self.presentObject(event.source)
+            orca.setLocusOfFocus(event, event.source, False)
+            return
+
         default.Script.onShowingChanged(self, event)
 
     def onTextDeleted(self, event):
diff --git a/src/orca/scripts/toolkits/Chromium/script.py b/src/orca/scripts/toolkits/Chromium/script.py
index 62b0198a5..e41293cae 100644
--- a/src/orca/scripts/toolkits/Chromium/script.py
+++ b/src/orca/scripts/toolkits/Chromium/script.py
@@ -336,8 +336,8 @@ class Script(web.Script):
             topLevel = self.utilities.topLevelObject(event.source)
             if self.utilities.canBeActiveWindow(topLevel):
                 orca_state.activeWindow = topLevel
-                notify = not self.utilities.isPopupMenuForCurrentItem(event.source)
-                orca.setLocusOfFocus(event, event.source, notify)
+                self.presentObject(event.source)
+                orca.setLocusOfFocus(event, event.source, False)
             return
 
         if super().onShowingChanged(event):
@@ -406,17 +406,29 @@ class Script(web.Script):
         # If this is a frame for a popup menu, we don't want to treat
         # it like a proper window:activate event because it's not as
         # far as the end-user experience is concerned.
-        activeItem = self.utilities.popupMenuForFrame(event.source)
-        if activeItem:
-            selected = self.utilities.selectedChildren(activeItem)
+        menu = self.utilities.popupMenuForFrame(event.source)
+        if menu:
+            orca_state.activeWindow = event.source
+
+            activeItem = None
+            selected = self.utilities.selectedChildren(menu)
             if len(selected) == 1:
                 activeItem = selected[0]
 
-            msg = "CHROMIUM: Setting locusOfFocus to %s" % activeItem
-            orca_state.activeWindow = event.source
-            orca.setLocusOfFocus(event, activeItem)
+            if activeItem:
+                # If this is the popup menu for the locusOfFocus, we don't want to
+                # present the popup menu as part of the new ancestry of activeItem.
+                if self.utilities.isPopupMenuForCurrentItem(menu):
+                    orca.setLocusOfFocus(event, menu, False)
+
+                msg = "CHROMIUM: Setting locusOfFocus to active item %s" % activeItem
+                orca.setLocusOfFocus(event, activeItem)
+                debug.println(debug.LEVEL_INFO, msg, True)
+                return
+
+            msg = "CHROMIUM: Setting locusOfFocus to popup menu %s" % menu
+            orca.setLocusOfFocus(event, menu)
             debug.println(debug.LEVEL_INFO, msg, True)
-            return
 
         if super().onWindowActivated(event):
             return
diff --git a/src/orca/scripts/toolkits/Chromium/script_utilities.py 
b/src/orca/scripts/toolkits/Chromium/script_utilities.py
index f483ab5c0..fe99fce41 100644
--- a/src/orca/scripts/toolkits/Chromium/script_utilities.py
+++ b/src/orca/scripts/toolkits/Chromium/script_utilities.py
@@ -160,22 +160,6 @@ class Utilities(web.Utilities):
 
         return result
 
-    def isMenuWithNoSelectedChild(self, obj):
-        if not obj:
-            return False
-
-        try:
-            role = obj.getRole()
-        except:
-            msg = "CHROMIUM: Exception getting role for %s" % obj
-            debug.println(debug.LEVEL_INFO, msg, True)
-            return False
-
-        if role != pyatspi.ROLE_MENU:
-            return False
-
-        return not self.selectedChildCount(obj)
-
     def isMenuInCollapsedSelectElement(self, obj):
         try:
             role = obj.getRole()
@@ -224,10 +208,7 @@ class Utilities(web.Utilities):
         if not self.treatAsMenu(orca_state.locusOfFocus):
             return False
 
-        if obj.name and obj.name == orca_state.locusOfFocus.name:
-            return obj.getRole() == pyatspi.ROLE_MENU
-
-        return False
+        return super().isPopupMenuForCurrentItem(obj)
 
     def isFrameForPopupMenu(self, obj):
         try:
diff --git a/src/orca/scripts/web/speech_generator.py b/src/orca/scripts/web/speech_generator.py
index 0eb41b606..44abb3391 100644
--- a/src/orca/scripts/web/speech_generator.py
+++ b/src/orca/scripts/web/speech_generator.py
@@ -334,6 +334,12 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
             debug.println(debug.LEVEL_INFO, msg, True)
             return []
 
+        role = args.get('role', obj.getRole())
+        if role == pyatspi.ROLE_MENU and self._script.utilities.isPopupMenuForCurrentItem(obj):
+            msg = "WEB: %s is popup menu for current item." % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return []
+
         if self._script.utilities.isContentEditableWithEmbeddedObjects(obj) \
            or self._script.utilities.isDocument(obj):
             lastKey, mods = self._script.utilities.lastKeyAndModifiers()
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index 957cbf941..f85c67785 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -177,6 +177,13 @@ class SpeechGenerator(generator.Generator):
         If the label cannot be found, the name will be used instead.
         If the name cannot be found, an empty array will be returned.
         """
+
+        role = args.get('role', obj.getRole())
+        if role == pyatspi.ROLE_MENU and self._script.utilities.isPopupMenuForCurrentItem(obj):
+            msg = 'SPEECH GENERATOR: %s is popup menu for current item.' % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return []
+
         result = []
         result.extend(self._generateLabel(obj, **args))
         if not result:
@@ -2341,6 +2348,9 @@ class SpeechGenerator(generator.Generator):
         if position < 0 or total < 0:
             return []
 
+        if obj.getRole() == pyatspi.ROLE_MENU and total == 1:
+            return []
+
         position += 1
         result.append(self._script.formatting.getString(
                               mode='speech',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]