[orca] Begin rewrite of text-selection handling



commit 4d3f64f53e7c82ce6eb37b7509dd31ec0cae154d
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Thu Dec 26 21:09:28 2013 -0500

    Begin rewrite of text-selection handling

 src/orca/script_utilities.py                      |  103 ------
 src/orca/scripts/apps/evince/Makefile.am          |    3 +-
 src/orca/scripts/apps/evince/script.py            |   33 ++-
 src/orca/scripts/apps/evince/script_utilities.py  |   74 ----
 src/orca/scripts/apps/soffice/script.py           |    6 +
 src/orca/scripts/apps/soffice/script_utilities.py |   28 --
 src/orca/scripts/default.py                       |  398 ++++-----------------
 src/orca/scripts/toolkits/GAIL/script.py          |   10 +
 src/orca/scripts/toolkits/Gecko/script.py         |    3 +
 src/orca/scripts/toolkits/WebKitGtk/script.py     |   15 -
 src/orca/scripts/toolkits/gtk/script.py           |    9 +
 11 files changed, 134 insertions(+), 548 deletions(-)
---
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index 470c147..773afcf 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -1873,35 +1873,6 @@ class Utilities:
 
         return [currentSelected, otherSelected]
 
-    @staticmethod
-    def isTextSelected(obj, startOffset, endOffset):
-        """Returns an indication of whether the text is selected by
-        comparing the text offset with the various selected regions of
-        text for this accessible object.
-
-        Arguments:
-        - obj: the Accessible object.
-        - startOffset: text start offset.
-        - endOffset: text end offset.
-
-        Returns an indication of whether the text is selected.
-        """
-
-        if startOffset == endOffset:
-            return False
-
-        try:
-            text = obj.queryText()
-        except:
-            return False
-
-        for i in range(text.getNSelections()):
-            [startSelOffset, endSelOffset] = text.getSelection(i)
-            if (startOffset >= startSelOffset) and (endOffset <= endSelOffset):
-                return True
-
-        return False
-
     def isWordMisspelled(self, obj, offset):
         """Identifies if the current word is flagged as misspelled by the
         application. Different applications and toolkits flag misspelled
@@ -1918,80 +1889,6 @@ class Utilities:
 
         return False
 
-    def offsetsForPhrase(self, obj):
-        """Return the start and end offset for the given phrase
-
-        Arguments:
-        - obj: the Accessible object
-        """
-
-        try:
-            text = obj.queryText()
-        except:
-            return [0, 0]
-
-        lastPos = self._script.pointOfReference.get("lastCursorPosition")
-        startOffset = lastPos[1]
-        endOffset = text.caretOffset
-
-        # Swap values if in wrong order.
-        #
-        if (startOffset > endOffset and endOffset != -1) or startOffset == -1:
-            temp = endOffset
-            endOffset = startOffset
-            startOffset = temp
-
-        return [startOffset, endOffset]
-
-    def offsetsForLine(self, obj):
-        """Return the start and end offset for the given line
-
-        Arguments:
-        - obj: the Accessible object
-        """
-
-        lineAndOffsets = self._script.getTextLineAtCaret(obj)
-        return [lineAndOffsets[1], lineAndOffsets[2]]
-
-    def offsetsForWord(self, obj):
-        """Return the start and end offset for the given word
-
-        Arguments:
-        - obj: the Accessible object
-        """
-
-        try:
-            text = obj.queryText()
-        except:
-            return [0, 0]
-
-        wordAndOffsets = text.getTextAtOffset(
-            text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_START)
-
-        return [wordAndOffsets[1], wordAndOffsets[2]]
-
-    def offsetsForChar(self, obj):
-        """Return the start and end offset for the given character
-
-        Arguments:
-        - obj: the Accessible object
-        """
-
-        try:
-            text = obj.queryText()
-        except:
-            return [0, 0]
-
-        lastKey, mods = self.lastKeyAndModifiers()
-        if mods & settings.SHIFT_MODIFIER_MASK and lastKey == "Right":
-            startOffset = text.caretOffset - 1
-            endOffset = text.caretOffset
-        else:
-            startOffset = text.caretOffset
-            endOffset = text.caretOffset + 1
-
-        return [startOffset, endOffset]
-
     @staticmethod
     def queryNonEmptyText(obj):
         """Get the text interface associated with an object, if it is
diff --git a/src/orca/scripts/apps/evince/Makefile.am b/src/orca/scripts/apps/evince/Makefile.am
index 4f2bb21..390ed88 100644
--- a/src/orca/scripts/apps/evince/Makefile.am
+++ b/src/orca/scripts/apps/evince/Makefile.am
@@ -1,6 +1,5 @@
 orca_python_PYTHON = \
        __init__.py \
-       script.py \
-       script_utilities.py
+       script.py
 
 orca_pythondir=$(pkgpythondir)/scripts/apps/evince
diff --git a/src/orca/scripts/apps/evince/script.py b/src/orca/scripts/apps/evince/script.py
index dfa55cf..d09cd3b 100644
--- a/src/orca/scripts/apps/evince/script.py
+++ b/src/orca/scripts/apps/evince/script.py
@@ -2,6 +2,8 @@
 #
 # Copyright 2013 The Orca Team.
 #
+# Author: Joanmarie Diggs <jdiggs igalia com>
+#
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
 # License as published by the Free Software Foundation; either
@@ -32,7 +34,6 @@ import orca.orca_state as orca_state
 import orca.scripts.toolkits.gtk as gtk
 import orca.settings as settings
 from orca.structural_navigation import StructuralNavigation
-from .script_utilities import Utilities
 
 ########################################################################
 #                                                                      #
@@ -69,11 +70,6 @@ class Script(gtk.Script):
 
         return keyBindings
 
-    def getUtilities(self):
-        """Returns the utilites for this script."""
-
-        return Utilities(self)
-
     def getStructuralNavigation(self):
         """Returns the 'structural navigation' class for this script."""
 
@@ -126,3 +122,28 @@ class Script(gtk.Script):
             return
  
         gtk.Script.onShowingChanged(self, event)
+
+    def onTextSelectionChanged(self, event):
+        """Callback for object:text-selection-changed accessibility events."""
+
+        # Two functionally different objects (pages of a PDF) are currently
+        # contained in a single accessible object whose contents change. As
+        # a result, when a new text selection spans two pages, we have stored
+        # data for our previous location that makes no sense because that
+        # location no longer exists.
+
+        obj = event.source
+        textSelections = self.pointOfReference.get('textSelections', {})
+        oldStart, oldEnd = textSelections.get(hash(obj), (0, 0))
+
+        crossedPages = False
+        keyString, mods = self.utilities.lastKeyAndModifiers()
+        if keyString in ["Down", "Page_Down", "Right", "End"]:
+            crossedPages = oldStart > obj.queryText().caretOffset
+        elif keyString in ["Up", "Page_Up", "Left", "Home"]:
+            crossedPages = oldEnd < obj.queryText().caretOffset
+
+        if crossedPages:
+            self.pointOfReference['textSelections'] = {}
+
+        gtk.Script.onTextSelectionChanged(self, event)
diff --git a/src/orca/scripts/apps/soffice/script.py b/src/orca/scripts/apps/soffice/script.py
index 8066fcc..834427d 100644
--- a/src/orca/scripts/apps/soffice/script.py
+++ b/src/orca/scripts/apps/soffice/script.py
@@ -908,6 +908,12 @@ class Script(default.Script):
         if parent and parent.getRoleName() == 'text frame':
             return
 
+        obj, offset = self.pointOfReference.get("lastCursorPosition", (None, -1))
+        textSelections = self.pointOfReference.get('textSelections', {})
+        start, end = textSelections.get(hash(obj), (0, 0))
+        if start != end:
+            return
+
         default.Script.onFocusedChanged(self, event)
 
     def onCaretMoved(self, event):
diff --git a/src/orca/scripts/apps/soffice/script_utilities.py 
b/src/orca/scripts/apps/soffice/script_utilities.py
index 271daca..d038886 100644
--- a/src/orca/scripts/apps/soffice/script_utilities.py
+++ b/src/orca/scripts/apps/soffice/script_utilities.py
@@ -589,34 +589,6 @@ class Utilities(script_utilities.Utilities):
 
         return error == "misspelled"
 
-    def substring(self, obj, startOffset, endOffset):
-        """Returns the substring of the given object's text specialization.
-
-        NOTE: This is here to handle the problematic implementation of
-        getText by OpenOffice.  See the bug discussion at:
-
-           http://bugzilla.gnome.org/show_bug.cgi?id=356425)
-
-        Once the OpenOffice issue has been resolved, this method probably
-        should be removed.
-
-        Arguments:
-        - obj: an accessible supporting the accessible text specialization
-        - startOffset: the starting character position
-        - endOffset: the ending character position
-        """
-
-        text = obj.queryText().getText(0, -1)
-        if startOffset >= len(text):
-            startOffset = len(text) - 1
-        if endOffset == -1:
-            endOffset = len(text)
-        elif startOffset >= endOffset:
-            endOffset = startOffset + 1
-        string = text[max(0, startOffset):min(len(text), endOffset)]
-
-        return string
-
     def textAttributes(self, acc, offset, get_defaults=False):
         """Get the text attributes run for a given offset in a given accessible
 
diff --git a/src/orca/scripts/default.py b/src/orca/scripts/default.py
index 9e830b2..426b758 100644
--- a/src/orca/scripts/default.py
+++ b/src/orca/scripts/default.py
@@ -739,6 +739,9 @@ class Script(script.Script):
             pass
         else:
             self._saveLastCursorPosition(obj, text.caretOffset)
+            textSelections = self.pointOfReference.get('textSelections', {})
+            textSelections[hash(obj)] = text.getSelection(0)
+            self.pointOfReference['textSelections'] = textSelections
 
         # We want to save the current row and column of a newly focused
         # or selected table cell so that on subsequent cell focus/selection
@@ -2183,6 +2186,11 @@ class Script(script.Script):
         if self.flatReviewContext:
             self.toggleFlatReviewMode()
 
+        text = event.source.queryText()
+        self._saveLastCursorPosition(event.source, text.caretOffset)
+        if text.getNSelections():
+            return
+
         self._presentTextAtNewCaretPosition(event)
 
     def onDocumentReload(self, event):
@@ -2699,91 +2707,57 @@ class Script(script.Script):
             self.echoPreviousWord(event.source)
 
     def onTextSelectionChanged(self, event):
-        """Called when an object's text selection changes.
-
-        Arguments:
-        - event: the Event
-        """
+        """Callback for object:text-selection-changed accessibility events."""
 
         obj = event.source
-        spokenRange = self.pointOfReference.get("spokenTextRange") or [0, 0]
-        startOffset, endOffset = spokenRange
-
-        if not obj.getState().contains(pyatspi.STATE_FOCUSED):
-            # We're selecting across paragraph (or other text object)
-            # boundaries. If we're here, the selection has changed in
-            # an object which does not have the caret. We need to try
-            # to sort this out.
-            #
-            lastPos = self.pointOfReference.get("lastCursorPosition")
-            if not lastPos:
-                # We have no point of reference. Bail.
-                #
-                return
-            elif endOffset - startOffset > 1:
-                # We're coming at the line from below. And didn't just
-                # land on a blank/empty line. We have other methods for
-                # dealing with this situation.
-                #
-                return
-            else:
-                # If we do a select all in a document in which each
-                # paragraph is a separate accessible object, we'll
-                # get an event for each of those objects. We don't
-                # want to repeat "(un)selected". See bug #583811.
-                #
-                diff = lastPos[0].getIndexInParent() - obj.getIndexInParent()
-                if abs(diff) > 1:
-                    # We can skip this one because we'll do the
-                    # announcement based on another one.
-                    #
-                    return
-                elif startOffset > 0 and startOffset == endOffset:
-                    try:
-                        text = lastPos[0].queryText()
-                    except:
-                        pass
-                    else:
-                        if startOffset == text.characterCount:
-                            return
-
-            # We must be approaching from the top, left, or right. Or
-            # from below but we've found a blank line. Our stored point
-            # of reference tells us our caret location. Figure out how
-            # we got here by looking at our position with respect to
-            # the event under consideration.
-            #
-            relationType = None
-            for relation in lastPos[0].getRelationSet():
-                if relation.getRelationType() in [pyatspi.RELATION_FLOWS_FROM,
-                                                  pyatspi.RELATION_FLOWS_TO] \
-                   and self.utilities.isSameObject(obj, relation.getTarget(0)):
-                    relationType = relation.getRelationType()
-                    break
+        self.updateBraille(obj)
 
-            # If there's a completely blank line in between our previous
-            # and current locations, where we came from will lack any
-            # offically-selectable characters. As a result, we won't
-            # indicate when a blank line has been selected. Under these
-            # conditions, we'll try to backtrack further.
-            #
-            endOffset = 0
-            while obj and not endOffset:
-                try:
-                    endOffset = obj.queryText().characterCount
-                    startOffset = max(0, endOffset - 1)
-                except:
-                    pass
+        # Note: This guesswork to figure out what actually changed with respect
+        # to text selection will get eliminated once the new text-selection API
+        # is added to ATK and implemented by the toolkits. (BGO 638378)
 
-                if not endOffset:
-                    for relation in obj.getRelationSet():
-                        if relation.getRelationType() == relationType:
-                            obj = relation.getTarget(0)
-                            break
-                    else:
-                        break
+        textSelections = self.pointOfReference.get('textSelections', {})
+        oldStart, oldEnd = textSelections.get(hash(obj), (0, 0))
 
-        self.speakTextSelectionState(obj, startOffset, endOffset)
+        # TODO: JD - this doesn't yet handle the case of multiple non-contiguous
+        # selections in a single accessible object.
+
+        text = obj.queryText()
+        newStart, newEnd = text.getSelection(0)
+        textSelections[hash(obj)] = newStart, newEnd
+        self.pointOfReference['textSelections'] = textSelections
+
+        nSelections = text.getNSelections()
+        handled = self._speakTextSelectionState(nSelections)
+        if handled:
+            return
+
+        changes = []
+        oldChars = set(range(oldStart, oldEnd))
+        newChars = set(range(newStart, newEnd))
+        if not oldChars.union(newChars):
+            return
+
+        if oldChars and newChars and not oldChars.intersection(newChars):
+            # A simultaneous unselection and selection centered at one offset.
+            changes.append([oldStart, oldEnd, messages.TEXT_UNSELECTED])
+            changes.append([newStart, newEnd, messages.TEXT_SELECTED])
+        else:
+            change = sorted(oldChars.symmetric_difference(newChars))
+            if not change:
+                return
+
+            changeStart, changeEnd = change[0], change[-1] + 1
+            if oldChars < newChars:
+                changes.append([changeStart, changeEnd, messages.TEXT_SELECTED])
+            else:
+                changes.append([changeStart, changeEnd, messages.TEXT_UNSELECTED])
+
+        speakMessage = not _settingsManager.getSetting('onlySpeakDisplayedText')
+        for start, end, message in changes:
+            self.sayPhrase(obj, start, end)
+            if speakMessage:
+                self.speakMessage(message, interrupt=False)
 
     def onColumnReordered(self, event):
         """Called whenever the columns in a table are reordered.
@@ -2976,132 +2950,38 @@ class Script(script.Script):
         keyString, mods = self.utilities.lastKeyAndModifiers()
         if not keyString:
             return
+
         isControlKey = mods & settings.CTRL_MODIFIER_MASK
-        isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
-        lastPos = self.pointOfReference.get("lastCursorPosition")
-        hasLastPos = (lastPos != None)
 
         if keyString in ["Up", "Down"]:
-            # If the user has typed Shift-Up or Shift-Down, then we want
-            # to speak the text that has just been selected or unselected,
-            # otherwise we speak the new line where the text cursor is
-            # currently positioned.
-            #
-            if hasLastPos and isShiftKey and not isControlKey:
-                if keyString == "Up":
-                    # If we have just crossed a paragraph boundary with
-                    # Shift+Up, what we've selected in this object starts
-                    # with the current offset and goes to the end of the
-                    # paragraph.
-                    #
-                    if not self.utilities.isSameObject(lastPos[0], obj):
-                        [startOffset, endOffset] = \
-                            text.caretOffset, text.characterCount
-                    else:
-                        [startOffset, endOffset] = \
-                            self.utilities.offsetsForPhrase(obj)
-                    self.sayPhrase(obj, startOffset, endOffset)
-                    selSpoken = self._speakContiguousSelection(obj,
-                                                   pyatspi.RELATION_FLOWS_TO)
-                else:
-                    selSpoken = self._speakContiguousSelection(obj,
-                                                 pyatspi.RELATION_FLOWS_FROM)
-
-                    # If we have just crossed a paragraph boundary with
-                    # Shift+Down, what we've selected in this object starts
-                    # with the beginning of the paragraph and goes to the
-                    # current offset.
-                    #
-                    if not self.utilities.isSameObject(lastPos[0], obj):
-                        [startOffset, endOffset] = 0, text.caretOffset
-                    else:
-                        [startOffset, endOffset] \
-                            = self.utilities.offsetsForPhrase(obj)
-
-                    if startOffset != endOffset:
-                        self.sayPhrase(obj, startOffset, endOffset)
-
-            else:
-                [startOffset, endOffset] = self.utilities.offsetsForLine(obj)
-                self.sayLine(obj)
+            self.sayLine(obj)
 
         elif keyString in ["Left", "Right"]:
-            # If the user has typed Control-Shift-Up or Control-Shift-Dowm,
-            # then we want to speak the text that has just been selected
-            # or unselected, otherwise if the user has typed Control-Left
-            # or Control-Right, we speak the current word otherwise we speak
-            # the character at the text cursor position.
-            #
-            inNewObj = hasLastPos \
-                       and not self.utilities.isSameObject(lastPos[0], obj)
-
-            if hasLastPos and not inNewObj and isShiftKey and isControlKey:
-                [startOffset, endOffset] = self.utilities.offsetsForPhrase(obj)
-                self.sayPhrase(obj, startOffset, endOffset)
-            elif isControlKey and not isShiftKey:
-                [startOffset, endOffset] = self.utilities.offsetsForWord(obj)
-                if startOffset == endOffset:
-                    self.sayCharacter(obj)
-                else:
-                    self.sayWord(obj)
+            if isControlKey:
+                self.sayWord(obj)
             else:
-                [startOffset, endOffset] = self.utilities.offsetsForChar(obj)
                 self.sayCharacter(obj)
 
         elif keyString == "Page_Up":
-            # If the user has typed Control-Shift-Page_Up, then we want
-            # to speak the text that has just been selected or unselected,
-            # otherwise if the user has typed Control-Page_Up, then we
+            # TODO - JD: Why is Control special here?
+            # If the user has typed Control-Page_Up, then we
             # speak the character to the right of the current text cursor
             # position otherwise we speak the current line.
             #
-            if hasLastPos and isShiftKey and isControlKey:
-                [startOffset, endOffset] = self.utilities.offsetsForPhrase(obj)
-                self.sayPhrase(obj, startOffset, endOffset)
-            elif isControlKey:
-                [startOffset, endOffset] = self.utilities.offsetsForChar(obj)
+            if isControlKey:
                 self.sayCharacter(obj)
             else:
-                [startOffset, endOffset] = self.utilities.offsetsForLine(obj)
                 self.sayLine(obj)
 
         elif keyString == "Page_Down":
-            # If the user has typed Control-Shift-Page_Down, then we want
-            # to speak the text that has just been selected or unselected,
-            # otherwise if the user has just typed Page_Down, then we speak
-            # the current line.
-            #
-            if hasLastPos and isShiftKey and isControlKey:
-                [startOffset, endOffset] = self.utilities.offsetsForPhrase(obj)
-                self.sayPhrase(obj, startOffset, endOffset)
-            else:
-                [startOffset, endOffset] = self.utilities.offsetsForLine(obj)
-                self.sayLine(obj)
+            self.sayLine(obj)
 
         elif keyString in ["Home", "End"]:
-            # If the user has typed Shift-Home or Shift-End, then we want
-            # to speak the text that has just been selected or unselected,
-            # otherwise if the user has typed Control-Home or Control-End,
-            # then we speak the current line otherwise we speak the character
-            # to the right of the current text cursor position.
-            #
-            if hasLastPos and isShiftKey and not isControlKey:
-                [startOffset, endOffset] = self.utilities.offsetsForPhrase(obj)
-                self.sayPhrase(obj, startOffset, endOffset)
-            elif isControlKey:
-                [startOffset, endOffset] = self.utilities.offsetsForLine(obj)
+            if isControlKey:
                 self.sayLine(obj)
             else:
-                [startOffset, endOffset] = self.utilities.offsetsForChar(obj)
                 self.sayCharacter(obj)
 
-        else:
-            startOffset = text.caretOffset
-            endOffset = text.caretOffset
-
-        self._saveLastCursorPosition(obj, text.caretOffset)
-        self._saveSpokenTextRange(startOffset, endOffset)
-
     def __sayAllProgressCallback(self, context, progressType):
         # [[[TODO: WDW - this needs work.  Need to be able to manage
         # the monitoring of progress and couple that with both updating
@@ -3668,9 +3548,11 @@ class Script(script.Script):
         - endOffset: the end text offset.
         """
 
-        phrase = self.utilities.substring(obj, startOffset, endOffset)
+        phrase = self.utilities.expandEOCs(obj, startOffset, endOffset)
+        if not phrase:
+            return
 
-        if len(phrase) and phrase != "\n":
+        if len(phrase) > 1 or phrase.isalnum():
             if phrase.isupper():
                 voice = self.voices[settings.UPPERCASE_VOICE]
             else:
@@ -3679,8 +3561,6 @@ class Script(script.Script):
             phrase = self.utilities.adjustForRepeats(phrase)
             speech.speak(phrase, voice)
         else:
-            # Speak blank line if appropriate.
-            #
             self.sayCharacter(obj)
 
     def sayWord(self, obj):
@@ -4172,18 +4052,6 @@ class Script(script.Script):
             phoneticString = phonnames.getPhoneticName(character)
             speech.speak(phoneticString, voice)
 
-    def _saveSpokenTextRange(self, startOffset, endOffset):
-        """Save away the start and end offset of the range of text that
-        was spoken. It will be used by speakTextSelectionState, to try
-        to determine if the text was selected or unselected.
-
-        Arguments:
-        - startOffset: the start of the spoken text range.
-        - endOffset: the end of the spoken text range.
-        """
-
-        self.pointOfReference["spokenTextRange"] = [startOffset, endOffset]
-
     def _saveLastCursorPosition(self, obj, caretOffset):
         """Save away the current text cursor position for next time.
 
@@ -4194,179 +4062,69 @@ class Script(script.Script):
 
         self.pointOfReference["lastCursorPosition"] = [obj, caretOffset]
 
-    def _saveLastTextSelections(self, text):
-        """Save away the list of text selections for next time.
-
-        Arguments:
-        - text: the text object.
-        """
-
-        self.pointOfReference["lastSelections"] = []
-        for i in range(text.getNSelections()):
-            self.pointOfReference["lastSelections"].append(
-              text.getSelection(i))
-
     def _getCtrlShiftSelectionsStrings(self):
         return [messages.PARAGRAPH_SELECTED_DOWN,
                 messages.PARAGRAPH_UNSELECTED_DOWN,
                 messages.PARAGRAPH_SELECTED_UP,
                 messages.PARAGRAPH_UNSELECTED_UP]
 
-    def speakTextSelectionState(self, obj, startOffset, endOffset):
-        """Speak "selected" if the text was just selected, "unselected" if
-        it was just unselected.
-
-        Arguments:
-        - obj: the Accessible object.
-        - startOffset: text start offset.
-        - endOffset: text end offset.
-        """
+    def _speakTextSelectionState(self, nSelections):
+        """Hacky method to speak special cases without any valid sanity
+        checking. It is not long for this world. Do not call it."""
 
         if _settingsManager.getSetting('onlySpeakDisplayedText'):
-            return
-
-        try:
-            text = obj.queryText()
-        except:
-            return
+            return False
 
-        # Handle special cases.
-        #
-        # Control-Shift-Page_Down:
-        #          speak "line selected to end from previous cursor position".
-        # Control-Shift-Page_Up:
-        #        speak "line selected from start to previous cursor position".
-        #
-        # Shift-Page_Down:    speak "page <state> from cursor position".
-        # Shift-Page_Up:      speak "page <state> to cursor position".
-        #
-        # Control-Shift-Down: speak "line <state> down from cursor position".
-        # Control-Shift-Up:   speak "line <state> up from cursor position".
-        #
-        # Control-Shift-Home: speak "document <state> to cursor position".
-        # Control-Shift-End:  speak "document <state> from cursor position".
-        #
-        # Control-a:          speak "entire document selected".
-        #
-        # where <state> is either "selected" or "unselected" depending
-        # upon whether there are any text selections.
-        #
         eventStr, mods = self.utilities.lastKeyAndModifiers()
         isControlKey = mods & settings.CTRL_MODIFIER_MASK
         isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
-        selectedText = (text.getNSelections() != 0)
+        selectedText = nSelections > 0
 
-        specialCaseFound = False
+        line = None
         if (eventStr == "Page_Down") and isShiftKey and isControlKey:
-            specialCaseFound = True
             line = messages.LINE_SELECTED_RIGHT
         elif (eventStr == "Page_Up") and isShiftKey and isControlKey:
-            specialCaseFound = True
             line = messages.LINE_SELECTED_LEFT
-
         elif (eventStr == "Page_Down") and isShiftKey and not isControlKey:
-            specialCaseFound = True
             if selectedText:
                 line = messages.PAGE_SELECTED_DOWN
             else:
                 line = messages.PAGE_UNSELECTED_DOWN
-
         elif (eventStr == "Page_Up") and isShiftKey and not isControlKey:
-            specialCaseFound = True
             if selectedText:
                 line = messages.PAGE_SELECTED_UP
             else:
                 line = messages.PAGE_UNSELECTED_UP
-
         elif (eventStr == "Down") and isShiftKey and isControlKey:
-            specialCaseFound = True
             strings = self._getCtrlShiftSelectionsStrings()
             if selectedText:
                 line = strings[0]
             else:
                 line = strings[1]
-
         elif (eventStr == "Up") and isShiftKey and isControlKey:
-            specialCaseFound = True
             strings = self._getCtrlShiftSelectionsStrings()
             if selectedText:
                 line = strings[2]
             else:
                 line = strings[3]
-
         elif (eventStr == "Home") and isShiftKey and isControlKey:
-            specialCaseFound = True
             if selectedText:
                 line = messages.DOCUMENT_SELECTED_UP
             else:
                 line = messages.DOCUMENT_UNSELECTED_UP
-
         elif (eventStr == "End") and isShiftKey and isControlKey:
-            specialCaseFound = True
             if selectedText:
                 line = messages.DOCUMENT_SELECTED_DOWN
             else:
                 line = messages.DOCUMENT_SELECTED_UP
+        elif (eventStr == "A") and isControlKey and selectedText:
+            line = messages.DOCUMENT_SELECTED_ALL
 
-        elif (eventStr == "A") and isControlKey:
-            # The user has typed Control-A. Check to see if the entire
-            # document has been selected, and if so, let the user know.
-            #
-            charCount = text.characterCount
-            for i in range(0, text.getNSelections()):
-                [startOffset, endOffset] = text.getSelection(i)
-                if text.caretOffset == 0 and \
-                   startOffset == 0 and endOffset == charCount:
-                    specialCaseFound = True
-                    self.updateBraille(obj)
-                    line = messages.DOCUMENT_SELECTED_ALL
-
-        if specialCaseFound:
+        if line:
             speech.speak(line, None, False)
-            return
-        elif startOffset == endOffset:
-            return
-
-        try:
-            # If we are selecting by word, then there possibly will be
-            # whitespace characters on either end of the text. We adjust
-            # the startOffset and endOffset to exclude them.
-            #
-            try:
-                tmpStr = text.getText(startOffset, endOffset)
-            except:
-                tmpStr = ''
-            n = len(tmpStr)
-
-            # Don't strip whitespace if string length is one (might be a
-            # space).
-            #
-            if n > 1:
-                while endOffset > startOffset:
-                    if self.utilities.isWordDelimiter(tmpStr[n-1]):
-                        n -= 1
-                        endOffset -= 1
-                    else:
-                        break
-                n = 0
-                while startOffset < endOffset:
-                    if self.utilities.isWordDelimiter(tmpStr[n]):
-                        n += 1
-                        startOffset += 1
-                    else:
-                        break
-
-        except:
-            debug.printException(debug.LEVEL_FINEST)
-
-        if not _settingsManager.getSetting('onlySpeakDisplayedText'):
-            voice = self.voices.get(settings.SYSTEM_VOICE)
-            if self.utilities.isTextSelected(obj, startOffset, endOffset):
-                speech.speak(messages.TEXT_SELECTED, voice, False)
-            elif len(text.getText(startOffset, endOffset)):
-                speech.speak(messages.TEXT_UNSELECTED, voice, False)
+            return True
 
-        self._saveLastTextSelections(text)
+        return False
 
     def systemBeep(self):
         """Rings the system bell. This is really a hack. Ideally, we want
diff --git a/src/orca/scripts/toolkits/GAIL/script.py b/src/orca/scripts/toolkits/GAIL/script.py
index 1dde092..a97e232 100644
--- a/src/orca/scripts/toolkits/GAIL/script.py
+++ b/src/orca/scripts/toolkits/GAIL/script.py
@@ -28,6 +28,7 @@ __license__   = "LGPL"
 import pyatspi
 
 import orca.orca as orca
+import orca.orca_state as orca_state
 import orca.scripts.default as default
 
 class Script(default.Script):
@@ -88,3 +89,12 @@ class Script(default.Script):
 
             orca.setLocusOfFocus(event, obj)
             return
+
+    def onTextSelectionChanged(self, event):
+        """Callback for object:text-selection-changed accessibility events."""
+
+        obj = event.source
+        if not self.utilities.isSameObject(obj, orca_state.locusOfFocus):
+            return
+
+        default.Script.onTextSelectionChanged(self, event)
diff --git a/src/orca/scripts/toolkits/Gecko/script.py b/src/orca/scripts/toolkits/Gecko/script.py
index dbbc81e..68f8985 100644
--- a/src/orca/scripts/toolkits/Gecko/script.py
+++ b/src/orca/scripts/toolkits/Gecko/script.py
@@ -1824,6 +1824,9 @@ class Script(default.Script):
         if keyboardEvent.event_string in ["Page_Up", "Page_Down"]:
             return False
 
+        if keyboardEvent.modifiers & settings.SHIFT_MODIFIER_MASK:
+            return False
+
         if self._loadingDocumentContent:
             return False
 
diff --git a/src/orca/scripts/toolkits/WebKitGtk/script.py b/src/orca/scripts/toolkits/WebKitGtk/script.py
index 801c242..feaced0 100644
--- a/src/orca/scripts/toolkits/WebKitGtk/script.py
+++ b/src/orca/scripts/toolkits/WebKitGtk/script.py
@@ -294,21 +294,6 @@ class Script(default.Script):
         else:
             self.presentMessage(messages.PAGE_LOADING_END)
 
-    def onTextSelectionChanged(self, event):
-        """Called when an object's text selection changes.
-
-        Arguments:
-        - event: the Event
-        """
-
-        # The default script's method attempts to handle various and sundry
-        # complications that simply do not apply here.
-        #
-        spokenRange = self.pointOfReference.get("spokenTextRange") or [0, 0]
-        startOffset, endOffset = spokenRange
-
-        self.speakTextSelectionState(event.source, startOffset, endOffset)
-
     def sayCharacter(self, obj):
         """Speak the character at the caret.
 
diff --git a/src/orca/scripts/toolkits/gtk/script.py b/src/orca/scripts/toolkits/gtk/script.py
index cc35e9f..b7f78ff 100644
--- a/src/orca/scripts/toolkits/gtk/script.py
+++ b/src/orca/scripts/toolkits/gtk/script.py
@@ -122,3 +122,12 @@ class Script(default.Script):
 
             orca.setLocusOfFocus(event, obj)
             return
+
+    def onTextSelectionChanged(self, event):
+        """Callback for object:text-selection-changed accessibility events."""
+
+        obj = event.source
+        if not self.utilities.isSameObject(obj, orca_state.locusOfFocus):
+            return
+
+        default.Script.onTextSelectionChanged(self, event)


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