[orca] Fix for bgo#592708 - Orca should attempt to recognize Evo's Setup Assistant and present the (non-foc



commit 0edab327cfe143e80bec3e956f4825c39737e457
Author: Joanmarie Diggs <joanmarie diggs gmail com>
Date:   Sat Aug 22 19:37:09 2009 -0400

    Fix for bgo#592708 - Orca should attempt to recognize Evo's Setup Assistant and present the (non-focused) prompts

 src/orca/scripts/apps/evolution/script.py          |  307 +++++++++-----------
 .../scripts/apps/evolution/speech_generator.py     |   35 +++
 2 files changed, 170 insertions(+), 172 deletions(-)
---
diff --git a/src/orca/scripts/apps/evolution/script.py b/src/orca/scripts/apps/evolution/script.py
index 2335cac..1ca925e 100644
--- a/src/orca/scripts/apps/evolution/script.py
+++ b/src/orca/scripts/apps/evolution/script.py
@@ -73,13 +73,9 @@ class Script(default.Script):
         #
         self.spellCheckDialog = None
 
-        # Dictionary of Setup Assistant panels already handled.
+        # Last Setup Assistant panel spoken.
         #
-        self.setupPanels = {}
-
-        # Dictionary of Setup Assistant labels already handled.
-        #
-        self.setupLabels = {}
+        self.lastSetupPanel = None
 
         # The last row and column we were on in the mail message header list.
 
@@ -196,6 +192,47 @@ class Script(default.Script):
 
         return listeners
 
+    def findUnrelatedLabels(self, root):
+        """Returns a list containing all the unrelated (i.e., have no
+        relations to anything and are not a fundamental element of a
+        more atomic component like a combo box) labels under the given
+        root.  Note that the labels must also be showing on the display.
+
+        Arguments:
+        - root the Accessible object to traverse
+
+        Returns a list of unrelated labels under the given root.
+        """
+
+        labels = default.Script.findUnrelatedLabels(self, root)
+        for i, label in enumerate(labels):
+            if not label.getState().contains(pyatspi.STATE_SENSITIVE):
+                labels.remove(label)
+            else:
+                try:
+                    text = label.queryText()
+                except:
+                    pass
+                else:
+                    attr = text.getAttributes(0)
+                    if attr[0]:
+                        [charKeys, charDict] = \
+                            self.textAttrsToDictionary(attr[0])
+                        if charDict.get('weight', '400') == '700':
+                            if self.isWizard(root):
+                                # We've passed the wizard info at the top,
+                                # which is what we want to present. The rest
+                                # is noise.
+                                #
+                                return labels[0:i]
+                            else:
+                                # This label is bold and thus serving as a
+                                # heading. As such, it's not really unrelated.
+                                #
+                                labels.remove(label)
+
+        return labels
+
     def toggleReadMail(self, inputEvent):
         """ Toggle whether we present new mail if we not not the active script.+
         Arguments:
@@ -221,106 +258,6 @@ class Script(default.Script):
 
         return True
 
-    def speakSetupAssistantLabel(self, label):
-        """Perform a variety of tests on this Setup Assistant label to see
-        if we want to speak it.
-
-        Arguments:
-        - label: the Setup Assistant Label.
-        """
-
-        if label.getState().contains(pyatspi.STATE_SHOWING):
-            # We are only interested in a label if all the panels in the
-            # component hierarchy have states of ENABLED, SHOWING and VISIBLE.
-            # If this is not the case, then just return.
-            #
-            obj = label.parent
-            while obj and obj.getRole() != pyatspi.ROLE_APPLICATION:
-                if obj.getRole() == pyatspi.ROLE_PANEL:
-                    state = obj.getState()
-                    if not state.contains(pyatspi.STATE_ENABLED) or \
-                       not state.contains(pyatspi.STATE_SHOWING) or \
-                       not state.contains(pyatspi.STATE_VISIBLE):
-                        return
-                obj = obj.parent
-
-            # Each Setup Assistant screen has one label in the top left
-            # corner that describes what this screen is for. It has a text
-            # weight attribute of 800. We always speak those labels with
-            # " screen" appended.
-            #
-            try:
-                labelText = label.queryText()
-                if labelText:
-                    charAttributes = labelText.getAttributes(0)
-                    if charAttributes[0]:
-                        [charKeys, charDict] = \
-                            self.textAttrsToDictionary(charAttributes[0])
-                        weight = charDict.get('weight')
-                        if weight and weight == '800':
-                            text = self.getDisplayedText(label)
-
-                            # Only speak the screen label if we haven't already
-                            # done so.
-                            #
-                            if text and label not in self.setupLabels:
-                                # Translators: this is the name of a setup
-                                # assistant window/screen in Evolution.
-                                #
-                                speech.speak(_("%s screen") % text, None, False)
-                                self.setupLabels[label] = True
-
-                                # If the locus of focus is a push button that's
-                                # insensitive, speak/braille about it. (The
-                                # Identity screen has such a component).
-                                #
-                                if orca_state.locusOfFocus and \
-                                   orca_state.locusOfFocus.getRole() == \
-                                       pyatspi.ROLE_PUSH_BUTTON and \
-                                   (not orca_state.locusOfFocus.\
-                                                   getState().contains( \
-                                       pyatspi.STATE_SENSITIVE)):
-                                    self.updateBraille(orca_state.locusOfFocus)
-                                    speech.speak(
-                                        self.speechGenerator.generateSpeech(
-                                            orca_state.locusOfFocus))
-            except NotImplementedError:
-                pass
-
-            # It's possible to get multiple "object:state-changed:showing"
-            # events for the same label. If we've already handled this
-            # label, then just ignore it.
-            #
-            text = self.getDisplayedText(label)
-            if text and label not in self.setupLabels:
-                # Most of the Setup Assistant screens have a useful piece
-                # of text starting with the word "Please". We want to speak
-                # these. For the first screen, the useful piece of text
-                # starts with "Welcome". For the last screen, it starts
-                # with "Congratulations". Speak those too.
-                #
-                # Translators: we regret having to do this, but the
-                # translated string here has to match what the translated
-                # string is for Evolution.
-                #
-                if text.startswith(_("Please")) or \
-                    text.startswith(_("Welcome")) or \
-                    text.startswith(_("Congratulations")):
-                    speech.speak(text, None, False)
-                    self.setupLabels[label] = True
-
-    def handleSetupAssistantPanel(self, panel):
-        """Find all the labels in this Setup Assistant panel and see if
-        we want to speak them.
-
-        Arguments:
-        - panel: the Setup Assistant panel.
-        """
-
-        allLabels = self.findByRole(panel, pyatspi.ROLE_LABEL)
-        for label in allLabels:
-            self.speakSetupAssistantLabel(label)
-
     def readPageTab(self, tab):
         """Speak/Braille the given page tab. The speech verbosity is set
            to VERBOSITY_LEVEL_BRIEF for this operation and then restored
@@ -804,7 +741,6 @@ class Script(default.Script):
     # 8) Mail compose window: message area
     # 9) Spell Checking Dialog
     # 10) Mail view: message area - attachments.
-    # 11) Setup Assistant
 
     def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
         """Called when the visual object with focus changes.
@@ -1513,32 +1449,6 @@ class Script(default.Script):
             speech.speak(utterance)
             return
 
-        # 11) Setup Assistant.
-        #
-        # If the name of the frame of the object that currently has focus is
-        # "Evolution Setup Assistant", then empty out the two dictionaries
-        # containing which setup assistant panels and labels we've already
-        # seen.
-
-        obj = event.source.parent
-        while obj and obj.getRole() != pyatspi.ROLE_APPLICATION:
-            # Translators: this is the ending of the name of the
-            # Evolution Setup Assistant window.  The translated
-            # form has to match what Evolution is using.  We hate
-            # keying off stuff like this, but we're forced to do
-            # so in this case.
-            #
-            if obj.getRole() == pyatspi.ROLE_FRAME and \
-                (obj.name.endswith(_("Assistant")) or \
-                obj.name.startswith(_("Assistant"))):
-                debug.println(self.debugLevel,
-                              "evolution.locusOfFocusChanged - " \
-                              + "setup assistant.")
-                self.setupPanels = {}
-                self.setupLabels = {}
-                break
-            obj = obj.parent
-
         # For everything else, pass the focus event onto the parent class
         # to be handled in the default way.
         #
@@ -1584,51 +1494,104 @@ class Script(default.Script):
         - event: the Event
         """
 
-        if event.type.endswith("showing"):
-            # Check to see if this "object:state-changed:showing" event is
-            # for an object in the Setup Assistant by walking back up the
-            # object hierarchy until we get to the frame object and check
-            # to see if it has a name that ends with "Assistant", which is
-            # what we see when we configure Evolution for the first time
-            # and when we add new accounts.
-            #
-            obj = event.source.parent
-            while obj and obj.getRole() != pyatspi.ROLE_APPLICATION:
-                # Translators: this is the ending of the name of the
-                # Evolution Setup Assistant window.  The translated
-                # form has to match what Evolution is using.  We hate
-                # keying off stuff like this, but we're forced to do
-                # so in this case.
-                #
-                if obj.getRole() == pyatspi.ROLE_FRAME and \
-                    (obj.name.endswith(_("Assistant")) or \
-                    obj.name.startswith(_("Assistant"))):
-                    debug.println(self.debugLevel,
-                                  "evolution.onStateChanged - " \
-                                  + "setup assistant.")
-
-                    # If the event is for a label see if we want to speak it.
-                    #
-                    if event.source.getRole() == pyatspi.ROLE_LABEL:
-                        self.speakSetupAssistantLabel(event.source)
-                        break
-
-                    # If the event is for a panel and we haven't already
-                    # seen this panel, then handle it.
-                    #
-                    elif event.source.getRole() == pyatspi.ROLE_PANEL and \
-                        event.source not in self.setupPanels:
-                        self.handleSetupAssistantPanel(event.source)
-                        self.setupPanels[event.source] = True
-                        break
-
-                obj = obj.parent
+        if self.isWizardNewInfoEvent(event):
+            if event.source.getRole() == pyatspi.ROLE_PANEL:
+                self.lastSetupPanel = event.source
+            self.presentWizardNewInfo(self.getTopLevel(event.source))
+            return
 
         # For everything else, pass the event onto the parent class
         # to be handled in the default way.
         #
         default.Script.onStateChanged(self, event)
 
+    def presentWizardNewInfo(self, obj):
+        """Causes the new information displayed in a wizard to be presented
+        to the user.
+
+        Arguments:
+        - obj: the Accessible object
+        """
+
+        if not obj:
+            return
+
+        # TODO - JD: Presenting the Setup Assistant (or any Wizard) as a
+        # dialog means that we will repeat the dialog's name for each new
+        # "screen". We should consider a 'ROLE_WIZARD' or some other means
+        # for presenting these objects.
+        #
+        utterances = \
+            self.speechGenerator.generateSpeech(obj, role=pyatspi.ROLE_DIALOG)
+
+        # The following falls under the heading of "suck it and see." The
+        # worst case scenario is that we present the push button and then
+        # process a focus:/object:state-changed:focused event and present
+        # it.
+        #
+        if orca_state.locusOfFocus \
+           and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_PUSH_BUTTON \
+           and orca_state.locusOfFocus.getState().\
+               contains(pyatspi.STATE_FOCUSED):
+            utterances.append(
+                self.speechGenerator.generateSpeech(orca_state.locusOfFocus))
+
+        speech.speak(utterances)
+
+    def isWizard(self, obj):
+        """Returns True if this object is, or is within, a wizard.
+
+        Arguments:
+        - obj: the Accessible object
+        """
+
+        # The Setup Assistant is a frame whose child is a panel. That panel
+        # holds a bunch of other panels, one for each stage in the wizard.
+        # Only the active stage's panel has STATE_SHOWING. There is also
+        # one child of ROLE_FILLER which holds the buttons.
+        #
+        window = self.getTopLevel(obj) or obj
+        if window and window.getRole() == pyatspi.ROLE_FRAME \
+           and window.childCount and window[0].getRole() == pyatspi.ROLE_PANEL:
+            allPanels = panelsNotShowing = 0
+            for child in window[0]:
+                if child.getRole() == pyatspi.ROLE_PANEL:
+                    allPanels += 1
+                    if not child.getState().contains(pyatspi.STATE_SHOWING):
+                        panelsNotShowing += 1
+            if allPanels - panelsNotShowing == 1 \
+               and window[0].childCount - allPanels == 1:
+                return True
+
+        return False
+
+    def isWizardNewInfoEvent(self, event):
+        """Returns True if the event is judged to be the presentation of
+        new information in a wizard. This method should be subclassed by
+        application scripts as needed.
+
+        Arguments:
+        - event: the Accessible event being examined
+        """
+
+        if event.source.getRole() == pyatspi.ROLE_FRAME \
+           and (event.type.startswith("window:activate") \
+                or (event.type.startswith("object:state-changed:active") \
+                    and event.detail1 == 1)):
+            return self.isWizard(event.source)
+
+        elif event.source.getRole() == pyatspi.ROLE_PANEL \
+             and event.type.startswith("object:state-changed:showing") \
+             and event.detail1 == 1 \
+             and not self.isSameObject(event.source, self.lastSetupPanel):
+            rolesList = [pyatspi.ROLE_PANEL,
+                         pyatspi.ROLE_PANEL,
+                         pyatspi.ROLE_FRAME]
+            if self.isDesiredFocusedItem(event.source, rolesList):
+                return self.isWizard(event.source)
+
+        return False
+
     def isActivatableEvent(self, event):
         """Returns True if the given event is one that should cause this
         script to become the active script.  This is only a hint to
diff --git a/src/orca/scripts/apps/evolution/speech_generator.py b/src/orca/scripts/apps/evolution/speech_generator.py
index 6cf61b8..1518f71 100644
--- a/src/orca/scripts/apps/evolution/speech_generator.py
+++ b/src/orca/scripts/apps/evolution/speech_generator.py
@@ -129,3 +129,38 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
                     result.append(text)
 
         return result
+
+    def _generateUnrelatedLabels(self, obj, **args):
+        """Returns, as an array of strings (and possibly voice
+        specifications), all the labels which are underneath the obj's
+        hierarchy and which are not in a label for or labelled by
+        relation.
+        """
+
+        if not self._script.isWizard(obj):
+            return speech_generator.SpeechGenerator.\
+                _generateUnrelatedLabels(self, obj, **args)
+
+        result = []
+        labels = self._script.findUnrelatedLabels(obj)
+        for label in labels:
+            name = self._generateName(label, **args)
+            try:
+                text = label.queryText()
+            except:
+                pass
+            else:
+                attr = text.getAttributes(0)
+                if attr[0]:
+                    [charKeys, charDict] = \
+                        self._script.textAttrsToDictionary(attr[0])
+                    if charDict.get('weight', '400') == '800':
+                        # It's a new "screen" in the Setup Assistant.
+                        #
+                        name = self._script.getDisplayedText(label)
+                        # Translators: this is the name of a setup
+                        # assistant window/screen in Evolution.
+                        #
+                        name = [_("%s screen") % name]
+            result.extend(name)
+        return result



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