[orca] Begin cleaning up Live Region support in Gecko



commit 1e505b9d4222350c716964ef1ba65cfcfdb788ad
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Tue Jun 2 20:41:06 2015 -0400

    Begin cleaning up Live Region support in Gecko

 src/orca/liveregions.py                    |  202 ++++++++++++++--------------
 src/orca/script.py                         |    8 +-
 src/orca/scripts/toolkits/Gecko/keymaps.py |   23 ---
 src/orca/scripts/toolkits/Gecko/script.py  |   85 +++----------
 src/orca/structural_navigation.py          |    9 +-
 5 files changed, 134 insertions(+), 193 deletions(-)
---
diff --git a/src/orca/liveregions.py b/src/orca/liveregions.py
index c0dfa52..e00c6c6 100644
--- a/src/orca/liveregions.py
+++ b/src/orca/liveregions.py
@@ -4,9 +4,15 @@ import pyatspi
 import time
 from gi.repository import GLib
 
+from . import cmdnames
+from . import keybindings
 from . import messages
+from . import input_event
 from . import orca_state
 from . import speech
+from . import settings_manager
+
+_settingsManager = settings_manager.getManager()
 
 # define 'live' property types
 LIVE_OFF       = -1
@@ -58,47 +64,6 @@ class PriorityQueue:
         myfilter = lambda item: item[0] > priority
         self.queue = list(filter(myfilter, self.queue))
 
-    def clumpContents(self):
-        """ Combines messages with the same 'label' by appending newer  
-        'content' and removing the newer message.  This operation is only
-        applied to the next dequeued message for performance reasons and is
-        often applied in conjunction with filterContents() """
-        if len(self.queue):
-            newqueue = []
-            newqueue.append(self.queue[0])
-            targetlabels = newqueue[0][2]['labels']
-            targetcontent = newqueue[0][2]['content']
-            for i in range(1, len(self.queue)):
-                if self.queue[i][2]['labels'] == targetlabels:
-                    newqueue[0][2]['content'].extend \
-                                   (self.queue[i][2]['content'])
-                else:
-                    newqueue.append(self.queue[i]) 
-
-            self.queue = newqueue
-
-    def filterContents(self):
-        """ Combines utterances by eliminating repeated utterances and
-        utterances that are part of other utterances. """
-        if len(self.queue[0][2]['content']) > 1:
-            oldcontent = self.queue[0][2]['content']
-            newcontent = [oldcontent[0]]
-
-            for i in range(1, len(oldcontent)):
-                found = False
-                for j in range(len(newcontent)):
-                    if oldcontent[i].find(newcontent[j]) != -1 \
-                        or newcontent[j].find(oldcontent[i]) != -1: 
-                        if len(oldcontent[i]) > len(newcontent[j]):
-                            newcontent[j] = oldcontent[i]
-                        found = True
-                        break
-
-                if not found:
-                    newcontent.append(oldcontent[i])
-
-            self.queue[0][2]['content'] = newcontent
- 
     def __len__(self):
         """ Return the length of the queue """
         return len(self.queue)
@@ -110,6 +75,9 @@ class LiveRegionManager:
         # message priority queue
         self.msg_queue = PriorityQueue()
 
+        self.inputEventHandlers = self._getInputEventHandlers()
+        self.keyBindings = self._getKeyBindings()
+
         # Message cache.  Used to store up to 9 previous messages so user can
         # review if desired.
         self.msg_cache = []
@@ -137,6 +105,65 @@ class LiveRegionManager:
         script.bookmarks.addSaveObserver(self.bookmarkSaveHandler)
         script.bookmarks.addLoadObserver(self.bookmarkLoadHandler)
 
+    def _getInputEventHandlers(self):
+        handlers = {}
+
+        handlers["advanceLivePoliteness"] = \
+            input_event.InputEventHandler(
+                self.advancePoliteness,
+                cmdnames.LIVE_REGIONS_ADVANCE_POLITENESS)
+
+        handlers["setLivePolitenessOff"] = \
+            input_event.InputEventHandler(
+                self.setLivePolitenessOff,
+                cmdnames.LIVE_REGIONS_SET_POLITENESS_OFF)
+
+        handlers["monitorLiveRegions"] = \
+            input_event.InputEventHandler(
+                self.toggleMonitoring,
+                cmdnames.LIVE_REGIONS_MONITOR)
+
+        handlers["reviewLiveAnnouncement"] = \
+            input_event.InputEventHandler(
+                self.reviewLiveAnnouncement,
+                cmdnames.LIVE_REGIONS_REVIEW)
+
+        return handlers
+
+    def _getKeyBindings(self):
+        keyBindings = keybindings.KeyBindings()
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "backslash",
+                keybindings.defaultModifierMask,
+                keybindings.NO_MODIFIER_MASK,
+                self.inputEventHandlers.get("advanceLivePoliteness")))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "backslash",
+                keybindings.defaultModifierMask,
+                keybindings.SHIFT_MODIFIER_MASK,
+                self.inputEventHandlers.get("setLivePolitenessOff")))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "backslash",
+                keybindings.defaultModifierMask,
+                keybindings.ORCA_SHIFT_MODIFIER_MASK,
+                self.inputEventHandlers.get("monitorLiveRegions")))
+
+        for key in ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"]:
+            keyBindings.add(
+                keybindings.KeyBinding(
+                    key,
+                    keybindings.defaultModifierMask,
+                    keybindings.ORCA_MODIFIER_MASK,
+                    self.inputEventHandlers.get("reviewLiveAnnouncement")))
+
+        return keyBindings
+
     def reset(self):
         # First we will purge our politeness override dictionary of LIVE_NONE
         # objects that are not registered for this page
@@ -190,24 +217,9 @@ class LiveRegionManager:
         purging the message queue and outputting any queued messages that
         were queued up in the handleEvent() method.
         """
-        # If there are messages in the queue, we are monitoring, and we are not
-        # currently speaking then speak queued message.
-        # Note: Do all additional work within if statement to prevent
-        # it from being done for each event loop callback
-        # Note: isSpeaking() returns False way too early.  A strategy using
-        # a message length (in secs) could be used but don't forget many 
-        # parameters such as rate,expanded text and others must be considered.
-        if len(self.msg_queue) > 0 \
-                  and not speech.isSpeaking() \
-                  and orca_state.lastInputEvent \
-                  and time.time() - orca_state.lastInputEvent.time > 1:
-            # House cleaning on the message queue.  
-            # First we will purge the queue of old messages
+
+        if len(self.msg_queue) > 0:
             self.msg_queue.purgeByKeepAlive()
-            # Next, we will filter the messages
-            self.msg_queue.clumpContents()
-            self.msg_queue.filterContents()
-            # Let's get our queued information
             politeness, timestamp, message, obj = self.msg_queue.dequeue()
             # Form output message.  No need to repeat labels and content.
             # TODO: really needs to be tested in real life cases.  Perhaps
@@ -244,8 +256,14 @@ class LiveRegionManager:
                 retval.append(self._script.bookmarks.pathToObj(objectid))
         return retval
 
-    def advancePoliteness(self, obj):
+    def advancePoliteness(self, script, inputEvent):
         """Advance the politeness level of the given object"""
+
+        if not _settingsManager.getSetting('inferLiveRegions'):
+            self._script.presentMessage(messages.LIVE_REGIONS_OFF)
+            return
+
+        obj = orca_state.locusOfFocus
         utterances = []
         objectid = self._getObjectId(obj)
         uri = self._script.bookmarks.getURIKey()
@@ -282,16 +300,27 @@ class LiveRegionManager:
             self._script.speakContents(self._script.utilities.getObjectContentsAtOffset(
                                        self.lastliveobj, 0))
 
-    def reviewLiveAnnouncement(self, msgnum):
+    def reviewLiveAnnouncement(self, script, inputEvent):
         """Speak the given number cached message"""
+
+        msgnum = int(inputEvent.event_string[1:])
+        if not _settingsManager.getSetting('inferLiveRegions'):
+            self._script.presentMessage(messages.LIVE_REGIONS_OFF)
+            return
+
         if msgnum > len(self.msg_cache):
             self._script.presentMessage(messages.LIVE_REGIONS_NO_MESSAGE)
         else:
             self._script.presentMessage(self.msg_cache[-msgnum])
 
-    def setLivePolitenessOff(self):
+    def setLivePolitenessOff(self, script, inputEvent):
         """User toggle to set all live regions to LIVE_OFF or back to their
         original politeness."""
+
+        if not _settingsManager.getSetting('inferLiveRegions'):
+            self._script.presentMessage(messages.LIVE_REGIONS_OFF)
+            return
+
         # start at the document frame
         docframe = self._script.utilities.documentFrame()
         # get the URI of the page.  It is used as a partial key.
@@ -439,8 +468,10 @@ class LiveRegionManager:
             else:
                 return None
 
-        # Get the labeling information now that we have good content.
-        labels = self._getLabelsAsUtterances(event.source)
+        # Proper live regions typically come with proper aria labels. These
+        # labels are typically exposed as names. Failing that, descriptions.
+        # Looking for actual labels seems a non-performant waste of time.
+        labels = [event.source.name, event.source.description]
 
         # instantly send out notify messages
         if 'channel' in attrs and attrs['channel'] == 'notify':
@@ -463,42 +494,6 @@ class LiveRegionManager:
         if len(self.msg_cache) > CACHE_SIZE:
             self.msg_cache.pop(0)
 
-    def _getLabelsAsUtterances(self, obj):
-        """Get the labels for a given object"""
-        # try the Gecko label getter first
-        uttstring = self._script.utilities.displayedLabel(obj)
-        if uttstring:
-            return [uttstring.strip()]
-        # often we see a table cell.  I'll implement my own label getter
-        elif obj.getRole() == pyatspi.ROLE_TABLE_CELL \
-                           and obj.parent.childCount > 1:
-            # We will try the table interface first for it's parent
-            try:
-                itable = obj.parent.queryTable()
-                # I'm in a table, now what row are we in?  Look in the first 
-                # columm of that row.
-                #
-                # Note: getRowHeader() fails for most markup.  We will use the
-                # relation when the markup is good (when getRowHeader() works) 
-                # so we won't see this code in those cases.  
-                index = self._script.utilities.cellIndex(obj)
-                row = itable.getRowAtIndex(index)
-                header = itable.getAccessibleAt(row, 0)
-                # expand the header
-                return [self._script.utilities.expandEOCs(header).strip()]
-            except NotImplementedError:
-                pass
-
-            # Last ditch effort is to see if our parent is a table row <tr> 
-            # element.
-            parentattrs = self._getAttrDictionary(obj.parent) 
-            if 'tag' in parentattrs and parentattrs['tag'] == 'TR':
-                return [self._script.utilities.expandEOCs( \
-                                  obj.parent.getChildAtIndex(0)).strip()]
-
-        # Sorry, no valid labels found
-        return []
-
     def _getLiveType(self, obj):
         """Returns the live politeness setting for a given object. Also,
         registers LIVE_NONE objects in politeness overrides when monitoring."""
@@ -577,3 +572,12 @@ class LiveRegionManager:
             except Exception:
                 raise LookupError
             obj = obj.parent
+
+    def toggleMonitoring(self, script, inputEvent):
+        if not _settingsManager.getSetting('inferLiveRegions'):
+            _settingsManager.setSetting('inferLiveRegions', True)
+            self._script.presentMessage(messages.LIVE_REGIONS_MONITORING_ON)
+        else:
+            _settingsManager.setSetting('inferLiveRegions', False)
+            self.flushMessages()
+            self._script.presentMessage(messages.LIVE_REGIONS_MONITORING_OFF)
diff --git a/src/orca/script.py b/src/orca/script.py
index f0a4fea..dd76772 100644
--- a/src/orca/script.py
+++ b/src/orca/script.py
@@ -100,6 +100,9 @@ class Script:
         self.utilities = self.getUtilities()
         self.labelInference = self.getLabelInference()
         self.structuralNavigation = self.getStructuralNavigation()
+        self.bookmarks = self.getBookmarks()
+        self.liveRegionManager = self.getLiveRegionManager()
+
         self.chat = self.getChat()
         self.inputEventHandlers = {}
         self.pointOfReference = {}
@@ -113,7 +116,6 @@ class Script:
         self.generatorCache = {}
         self.eventCache = {}
         self.whereAmI = self.getWhereAmI()
-        self.bookmarks = self.getBookmarks()
         self.spellcheck = self.getSpellCheck()
         self.voices = settings.voices
         self.tutorialGenerator = self.getTutorialGenerator()
@@ -245,6 +247,10 @@ class Script:
         types = self.getEnabledStructuralNavigationTypes()
         return structural_navigation.StructuralNavigation(self, types)
 
+    def getLiveRegionManager(self):
+        """Returns the live region support for this script."""
+        return None
+
     def useStructuralNavigationModel(self):
         """Returns True if we should use structural navigation. Most
         scripts will have no need to override this.  Gecko does however
diff --git a/src/orca/scripts/toolkits/Gecko/keymaps.py b/src/orca/scripts/toolkits/Gecko/keymaps.py
index 471aa24..933070d 100644
--- a/src/orca/scripts/toolkits/Gecko/keymaps.py
+++ b/src/orca/scripts/toolkits/Gecko/keymaps.py
@@ -58,29 +58,6 @@ arrowKeymap = (
 )
 
 commonKeymap = (
-
-    # keybindings to provide chat room message history.
-    ("F1", defaultModifierMask, ORCA_MODIFIER_MASK, "reviewLiveAnnouncement"),
-    ("F2", defaultModifierMask, ORCA_MODIFIER_MASK, "reviewLiveAnnouncement"),
-    ("F3", defaultModifierMask, ORCA_MODIFIER_MASK, "reviewLiveAnnouncement"),
-    ("F4", defaultModifierMask, ORCA_MODIFIER_MASK, "reviewLiveAnnouncement"),
-    ("F5", defaultModifierMask, ORCA_MODIFIER_MASK, "reviewLiveAnnouncement"),
-    ("F6", defaultModifierMask, ORCA_MODIFIER_MASK, "reviewLiveAnnouncement"),
-    ("F7", defaultModifierMask, ORCA_MODIFIER_MASK, "reviewLiveAnnouncement"),
-    ("F8", defaultModifierMask, ORCA_MODIFIER_MASK, "reviewLiveAnnouncement"),
-    ("F9", defaultModifierMask, ORCA_MODIFIER_MASK, "reviewLiveAnnouncement"),
-
-    # misc
-    
-    ("backslash", defaultModifierMask, SHIFT_MODIFIER_MASK,
-    "setLivePolitenessOff"),
-
-    ("backslash", defaultModifierMask, ORCA_SHIFT_MODIFIER_MASK,
-    "monitorLiveRegions"),
-
-    ("backslash", defaultModifierMask, NO_MODIFIER_MASK,
-    "advanceLivePoliteness"),
-
     ("F12", defaultModifierMask, ORCA_MODIFIER_MASK,
     "toggleCaretNavigationHandler"),
 
diff --git a/src/orca/scripts/toolkits/Gecko/script.py b/src/orca/scripts/toolkits/Gecko/script.py
index b53634b..3722c2d 100644
--- a/src/orca/scripts/toolkits/Gecko/script.py
+++ b/src/orca/scripts/toolkits/Gecko/script.py
@@ -121,12 +121,6 @@ class Script(default.Script):
              Script.goBeginningOfLine,
              Script.goEndOfLine]
 
-        self._liveRegionFunctions = \
-            [Script.setLivePolitenessOff,
-             Script.advanceLivePoliteness,
-             Script.monitorLiveRegions,
-             Script.reviewLiveAnnouncement]
-
         if _settingsManager.getSetting('caretNavigationEnabled') == None:
             _settingsManager.setSetting('caretNavigationEnabled', True)
         if _settingsManager.getSetting('sayAllOnLoad') == None:
@@ -155,9 +149,6 @@ class Script(default.Script):
         #
         self._madeFindAnnouncement = False
 
-        # Create the live region manager and start the message manager
-        self.liveMngr = liveregions.LiveRegionManager(self)
-
         # For really large objects, a call to getAttributes can take up to
         # two seconds! This is a Firefox bug. We'll try to improve things
         # by storing attributes.
@@ -293,6 +284,11 @@ class Script(default.Script):
 
         return enabledTypes
 
+    def getLiveRegionManager(self):
+        """Returns the live region support for this script."""
+
+        return liveregions.LiveRegionManager(self)
+
     def getStructuralNavigation(self):
         """Returns the 'structural navigation' class for this script.
         """
@@ -309,6 +305,8 @@ class Script(default.Script):
         self.inputEventHandlers.update(\
             self.structuralNavigation.inputEventHandlers)
 
+        self.inputEventHandlers.update(self.liveRegionManager.inputEventHandlers)
+
         self.inputEventHandlers["goNextCharacterHandler"] = \
             input_event.InputEventHandler(
                 Script.goNextCharacter,
@@ -359,26 +357,6 @@ class Script(default.Script):
                 Script.goEndOfLine,
                 cmdnames.CARET_NAVIGATION_LINE_END)
 
-        self.inputEventHandlers["advanceLivePoliteness"] = \
-            input_event.InputEventHandler(
-                Script.advanceLivePoliteness,
-                cmdnames.LIVE_REGIONS_ADVANCE_POLITENESS)
-
-        self.inputEventHandlers["setLivePolitenessOff"] = \
-            input_event.InputEventHandler(
-                Script.setLivePolitenessOff,
-                cmdnames.LIVE_REGIONS_SET_POLITENESS_OFF)
-
-        self.inputEventHandlers["monitorLiveRegions"] = \
-            input_event.InputEventHandler(
-                Script.monitorLiveRegions,
-                cmdnames.LIVE_REGIONS_MONITOR)
-
-        self.inputEventHandlers["reviewLiveAnnouncement"] = \
-            input_event.InputEventHandler(
-                Script.reviewLiveAnnouncement,
-                cmdnames.LIVE_REGIONS_REVIEW)
-
         self.inputEventHandlers["toggleCaretNavigationHandler"] = \
             input_event.InputEventHandler(
                 Script.toggleCaretNavigation,
@@ -446,6 +424,10 @@ class Script(default.Script):
         for keyBinding in bindings.keyBindings:
             keyBindings.add(keyBinding)
 
+        liveRegionBindings = self.liveRegionManager.keyBindings
+        for keyBinding in liveRegionBindings.keyBindings:
+            keyBindings.add(keyBinding)
+
         return keyBindings
 
     def getAppPreferencesGUI(self):
@@ -662,9 +644,7 @@ class Script(default.Script):
                 self._lastCommandWasCaretNav = consumes
                 self._lastCommandWasStructNav = False
                 self._lastCommandWasMouseButton = False
-            elif handler \
-                 and (handler.function in self.structuralNavigation.functions \
-                      or handler.function in self._liveRegionFunctions):
+            elif handler and handler.function in self.structuralNavigation.functions:
                 consumes = self.useStructuralNavigationModel()
                 self._lastCommandWasCaretNav = False
                 self._lastCommandWasStructNav = consumes
@@ -681,9 +661,7 @@ class Script(default.Script):
                 self._lastCommandWasCaretNav = consumes
                 self._lastCommandWasStructNav = False
                 self._lastCommandWasMouseButton = False
-            elif handler \
-                 and (handler.function in self.structuralNavigation.functions \
-                      or handler.function in self._liveRegionFunctions):
+            elif handler and handler.function in self.structuralNavigation.functions:
                 consumes = self.useStructuralNavigationModel()
                 self._lastCommandWasCaretNav = False
                 self._lastCommandWasStructNav = consumes
@@ -1098,7 +1076,7 @@ class Script(default.Script):
         if self.utilities.handleAsLiveRegion(event):
             msg = "INFO: Event to be handled as live region"
             debug.println(debug.LEVEL_INFO, msg)
-            self.liveMngr.handleEvent(event)
+            self.liveRegionManager.handleEvent(event)
             return True
 
         if self._loadingDocumentContent:
@@ -1155,7 +1133,7 @@ class Script(default.Script):
         msg = "INFO: Updating loading state and resetting live regions"
         debug.println(debug.LEVEL_INFO, msg)
         self._loadingDocumentContent = False
-        self.liveMngr.reset()
+        self.liveRegionManager.reset()
         return True
 
     def onDocumentLoadStopped(self, event):
@@ -1293,7 +1271,7 @@ class Script(default.Script):
         if event.source.getRole() == pyatspi.ROLE_FRAME:
             msg = "INFO: Flusing messages from live region manager"
             debug.println(debug.LEVEL_INFO, msg)
-            self.liveMngr.flushMessages()
+            self.liveRegionManager.flushMessages()
 
         return True
 
@@ -1367,7 +1345,7 @@ class Script(default.Script):
         if self.utilities.handleAsLiveRegion(event):
             msg = "INFO: Event to be handled as live region"
             debug.println(debug.LEVEL_INFO, msg)
-            self.liveMngr.handleEvent(event)
+            self.liveRegionManager.handleEvent(event)
             return True
 
         if self.utilities.eventIsEOCAdded(event):
@@ -2049,35 +2027,6 @@ class Script(default.Script):
         self.utilities.setCaretPosition(obj, characterOffset)
         self.presentLine(obj, characterOffset)
 
-    def advanceLivePoliteness(self, inputEvent):
-        """Advances live region politeness level."""
-        if _settingsManager.getSetting('inferLiveRegions'):
-            self.liveMngr.advancePoliteness(orca_state.locusOfFocus)
-        else:
-            self.presentMessage(messages.LIVE_REGIONS_OFF)
-
-    def monitorLiveRegions(self, inputEvent):
-        if not _settingsManager.getSetting('inferLiveRegions'):
-            _settingsManager.setSetting('inferLiveRegions', True)
-            self.presentMessage(messages.LIVE_REGIONS_MONITORING_ON)
-        else:
-            _settingsManager.setSetting('inferLiveRegions', False)
-            self.liveMngr.flushMessages()
-            self.presentMessage(messages.LIVE_REGIONS_MONITORING_OFF)
-
-    def setLivePolitenessOff(self, inputEvent):
-        if _settingsManager.getSetting('inferLiveRegions'):
-            self.liveMngr.setLivePolitenessOff()
-        else:
-            self.presentMessage(messages.LIVE_REGIONS_OFF)
-
-    def reviewLiveAnnouncement(self, inputEvent):
-        if _settingsManager.getSetting('inferLiveRegions'):
-            self.liveMngr.reviewLiveAnnouncement( \
-                                    int(inputEvent.event_string[1:]))
-        else:
-            self.presentMessage(messages.LIVE_REGIONS_OFF)
-
     def enableStickyFocusMode(self, inputEvent):
         self._inFocusMode = True
         self._focusModeIsSticky = True
diff --git a/src/orca/structural_navigation.py b/src/orca/structural_navigation.py
index ee9a1cd..4584428 100644
--- a/src/orca/structural_navigation.py
+++ b/src/orca/structural_navigation.py
@@ -222,6 +222,8 @@ class StructuralNavigationObject:
                     modifiers,
                     self.inputEventHandlers[handlerName]))
 
+            self.functions.append(self.showList)
+
         # Set up the "at level" handlers (e.g. to navigate among headings
         # at the specified level).
         #
@@ -280,6 +282,8 @@ class StructuralNavigationObject:
                     modifiers,
                     self.inputEventHandlers[handlerName]))
 
+            self.functions.append(handler)
+
         # Set up the "directional" handlers (e.g. for table cells. Live
         # region support has a handler to go to the last live region,
         # so we'll handle that here as well).
@@ -713,7 +717,7 @@ class StructuralNavigation:
     #                                                                       #
     #########################################################################
 
-    def toggleStructuralNavigation(self, script, inputEvent):
+    def toggleStructuralNavigation(self, script, inputEvent, presentMessage=True):
         """Toggles structural navigation keys."""
 
         self.enabled = not self.enabled
@@ -724,7 +728,8 @@ class StructuralNavigation:
             string = messages.STRUCTURAL_NAVIGATION_KEYS_OFF
 
         debug.println(debug.LEVEL_CONFIGURATION, string)
-        self._script.presentMessage(string)
+        if presentMessage:
+            self._script.presentMessage(string)
 
     #########################################################################
     #                                                                       #


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