Index: src/orca/scripts/apps/gedit/speech_generator.py
===================================================================
--- src/orca/scripts/apps/gedit/speech_generator.py	(revision 3904)
+++ src/orca/scripts/apps/gedit/speech_generator.py	(working copy)
@@ -1,67 +0,0 @@
-# Orca
-#
-# Copyright 2004-2008 Sun Microsystems Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Library General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Library General Public License for more details.
-#
-# You should have received a copy of the GNU Library General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
-# Boston MA  02110-1301 USA.
-
-"""Custom script for gedit."""
-
-__id__        = "$Id$"
-__version__   = "$Revision$"
-__date__      = "$Date$"
-__copyright__ = "Copyright (c) 2004-2008 Sun Microsystems Inc."
-__license__   = "LGPL"
-
-import orca.speechgenerator as speechgenerator
-
-########################################################################
-#                                                                      #
-# The GEdit script class.                                              #
-#                                                                      #
-########################################################################
-
-class SpeechGenerator(speechgenerator.SpeechGenerator):
-    """Overrides _getSpeechForFrame so as to avoid digging into the
-    gedit hierarchy and tickling a bug in gedit.
-    """
-    def __init__(self, script):
-        speechgenerator.SpeechGenerator.__init__(self, script)
-
-    def _getSpeechForFrame(self, obj, already_focused):
-        """Get the speech for a frame.  [[[TODO: WDW - This avoids
-        digging into the component hierarchy so as to avoid tickling
-        a bug in GEdit (see module comment above).]]]
-
-        Arguments:
-        - obj: the frame
-        - already_focused: if False, the obj just received focus
-
-        Returns a list of utterances to be spoken for the object.
-        """
-
-        utterances = self._getDefaultSpeech(obj, already_focused)
-
-        # This will dig deep into the hierarchy, causing issues with
-        # gedit.  So, we won't do this.
-        #
-        #utterances = self._getSpeechForAlert(obj, already_focused)
-
-        self._debugGenerator("GEditSpeechGenerator._getSpeechForFrame",
-                             obj,
-                             already_focused,
-                             utterances)
-
-        return utterances
Index: src/orca/scripts/apps/gedit/Makefile.am
===================================================================
--- src/orca/scripts/apps/gedit/Makefile.am	(revision 3904)
+++ src/orca/scripts/apps/gedit/Makefile.am	(working copy)
@@ -2,8 +2,7 @@
 
 orca_python_PYTHON = \
 	__init__.py \
-	script.py \
-	speech_generator.py
+	script.py
 
 orca_pythondir=$(pyexecdir)/orca/scripts/apps/gedit
 
Index: src/orca/scripts/apps/gedit/script.py
===================================================================
--- src/orca/scripts/apps/gedit/script.py	(revision 3904)
+++ src/orca/scripts/apps/gedit/script.py	(working copy)
@@ -68,11 +68,6 @@
         self.lastBadWord = ''
         self.lastEventType = ''
 
-    def getSpeechGenerator(self):
-        """Returns the speech generator for this script.
-        """
-        return SpeechGenerator(self)
-
     def getListeners(self):
         """Sets up the AT-SPI event listeners for this script.
         """
@@ -298,6 +293,9 @@
         """
 
         obj = orca_state.locusOfFocus
+        if not obj:
+            return False
+
         rolesList1 = [pyatspi.ROLE_PUSH_BUTTON,
                       pyatspi.ROLE_FILLER,
                       pyatspi.ROLE_FILLER,
Index: src/orca/scripts/apps/soffice/script.py
===================================================================
--- src/orca/scripts/apps/soffice/script.py	(revision 3904)
+++ src/orca/scripts/apps/soffice/script.py	(working copy)
@@ -1848,6 +1848,11 @@
         - event: the Event
         """
 
+        # If the object is losing focus, then just ignore this event.
+        #
+        if event.detail1 == -1:
+            return
+
         if orca_state.lastNonModifierKeyEvent:
             event_string = orca_state.lastNonModifierKeyEvent.event_string
             isShiftKey = orca_state.lastNonModifierKeyEvent.modifiers \
@@ -1921,10 +1926,6 @@
         if (event_string == "Up" or event_string == "Down") and not isShiftKey:
             speech.stop()
 
-        # Speak a newline, if appropriate.
-        if self.speakNewLine(event.source):
-            speech.speakCharacter("\n", None)
-
         # Speak a blank line, if appropriate.
         if self.speakBlankLine(event.source):
             # Translators: "blank" is a short word to mean the
@@ -1934,63 +1935,6 @@
 
         default.Script.onCaretMoved(self, event)
 
-    def speakNewLine(self, obj):
-        """Returns True if a newline should be spoken.
-           Otherwise, returns False.
-        """
-
-        # Get the the AccessibleText interface.
-        try:
-            text = obj.queryText()
-        except NotImplementedError:
-            return False
-
-        # Was a left or right-arrow key pressed?
-        if not (orca_state.lastInputEvent and \
-                orca_state.lastInputEvent.__dict__.has_key("event_string")):
-            return False
-
-        lastKey = orca_state.lastNonModifierKeyEvent.event_string
-        if lastKey != "Left" and lastKey != "Right":
-            return False
-
-        # Was a control key pressed?
-        mods = orca_state.lastInputEvent.modifiers
-        isControlKey = mods & (1 << pyatspi.MODIFIER_CONTROL)
-
-        # Get the line containing the caret
-        caretOffset = text.caretOffset
-        line = text.getTextAtOffset(caretOffset, \
-            pyatspi.TEXT_BOUNDARY_LINE_START)
-        lineStart = line[1]
-        lineEnd = line[2]
-
-        if isControlKey:  # control-right-arrow or control-left-arrow
-
-            # Get the word containing the caret.
-            word = text.getTextAtOffset(caretOffset, \
-                pyatspi.TEXT_BOUNDARY_WORD_START)
-            wordStart = word[1]
-            wordEnd = word[2]
-
-            if lastKey == "Right":
-                if wordStart == lineStart:
-                    return True
-            else:
-                if wordEnd == lineEnd:
-                    return True
-
-        else:  # right arrow or left arrow
-
-            if lastKey == "Right":
-                if caretOffset == lineStart:
-                    return True
-            else:
-                if caretOffset == lineEnd:
-                    return True
-
-        return False
-
     def speakBlankLine(self, obj):
         """Returns True if a blank line should be spoken.
         Otherwise, returns False.
Index: src/orca/default.py
===================================================================
--- src/orca/default.py	(revision 3904)
+++ src/orca/default.py	(working copy)
@@ -3099,8 +3099,81 @@
         self.pointOfReference['oldName'] = event.source.name
         orca.visualAppearanceChanged(event, event.source)
 
+    def _speakContiguousSelection(self, obj, relationship):
+        """Check if the contiguous object has a selection. If it does, then
+        speak it. If the user pressed Shift-Down, then look for an object 
+        with a RELATION_FLOWS_FROM relationship. If they pressed Shift-Up,
+        then look for a RELATION_FLOWS_TO relationship.
+
+        Arguments:
+        - the current text object
+        - the flows relationship (RELATION_FLOWS_FROM or RELATION_FLOWS_TO).
+
+        Returns an indication of whether anything was spoken.
+        """
+
+        lastPos = self.pointOfReference.get("lastCursorPosition")
+
+        # Reasons to NOT speak contiguous selections:
+        #
+        # 1. The new cursor position is in the same object as the old
+        #    cursor position. (The change in selection is all within
+        #    the current object.)
+        # 2. If we are selecting up line by line from the beginning of
+        #    the line and have just crossed into a new object, the change
+        #    in selection is the previous line (which has just become
+        #    selected).  Nothing has changed on the line we came from.
+        #
+        if self.isSameObject(lastPos[0], obj) \
+           or relationship == pyatspi.RELATION_FLOWS_TO and lastPos[1] == 0:
+            return False
+
+        selSpoken = False
+        current = obj
+        for relation in current.getRelationSet():
+            if relation.getRelationType() == relationship:
+                obj = relation.getTarget(0)
+                objText = obj.queryText()
+
+                # When selecting down across paragraph boundaries, what
+                # we've (un)selected on (what is now) the previous line
+                # is from wherever the cursor used to be to the end of 
+                # the line.
+                #
+                if relationship == pyatspi.RELATION_FLOWS_FROM:
+                    start, end = lastPos[1], objText.characterCount
+
+                # When selecting up across paragraph boundaries, what
+                # we've (un)selected on (what is now) the next line is
+                # from the beginning of the line to wherever the cursor
+                # used to be.
+                #
+                else:
+                    start, end = 0, lastPos[1]
+
+                if objText.getNSelections() > 0:
+                    [textContents, startOffset, endOffset] = \
+                        self.whereAmI.getTextSelection(obj)
+
+                    # Now that we have the full selection, adjust based
+                    # on the relation type. (see above comment)
+                    #
+                    startOffset = start or startOffset
+                    endOffset = end or endOffset
+                    self.sayPhrase(obj, startOffset, endOffset)
+                    selSpoken = True
+                else:
+                    # We don't have selections in this object. But we're
+                    # here, which means that something is selected in a
+                    # neighboring object and the text in this object must
+                    # have just become unselected and needs to be spoken.
+                    #
+                    self.sayPhrase(obj, start, end)
+                    selSpoken = True
+
+        return selSpoken
+
     def _presentTextAtNewCaretPosition(self, event):
-
         obj = event.source
         text = obj.queryText()
 
@@ -3145,7 +3218,8 @@
         mods = orca_state.lastInputEvent.modifiers
         isControlKey = mods & (1 << pyatspi.MODIFIER_CONTROL)
         isShiftKey = mods & (1 << pyatspi.MODIFIER_SHIFT)
-        hasLastPos = self.pointOfReference.has_key("lastCursorPosition")
+        lastPos = self.pointOfReference.get("lastCursorPosition")
+        hasLastPos = (lastPos != None)
 
         if (keyString == "Up") or (keyString == "Down"):
             # If the user has typed Shift-Up or Shift-Down, then we want
@@ -3154,8 +3228,39 @@
             # currently positioned.
             #
             if hasLastPos and isShiftKey and not isControlKey:
-                [startOffset, endOffset] = self.getOffsetsForPhrase(obj)
-                self.sayPhrase(obj, startOffset, endOffset)
+                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.isSameObject(lastPos[0], obj):
+                        [startOffset, endOffset] = \
+                            text.caretOffset, text.characterCount
+                    else:
+                        [startOffset, endOffset] \
+                                             = self.getOffsetsForPhrase(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.isSameObject(lastPos[0], obj):
+                        [startOffset, endOffset] = 0, text.caretOffset
+                    else:
+                        [startOffset, endOffset] \
+                                             = self.getOffsetsForPhrase(obj)
+
+                    if startOffset != endOffset:
+                        self.sayPhrase(obj, startOffset, endOffset)
+
             else:
                 [startOffset, endOffset] = self.getOffsetsForLine(obj)
                 self.sayLine(obj)
@@ -3167,12 +3272,17 @@
             # or Control-Right, we speak the current word otherwise we speak
             # the character at the text cursor position.
             #
-            if hasLastPos and isShiftKey and isControlKey:
+            inNewObj = hasLastPos and not self.isSameObject(lastPos[0], obj)
+
+            if hasLastPos and not inNewObj and isShiftKey and isControlKey:
                 [startOffset, endOffset] = self.getOffsetsForPhrase(obj)
                 self.sayPhrase(obj, startOffset, endOffset)
-            elif isControlKey:
+            elif isControlKey and not inNewObj:
                 [startOffset, endOffset] = self.getOffsetsForWord(obj)
-                self.sayWord(obj)
+                if startOffset == endOffset:
+                    self.sayCharacter(obj)
+                else:
+                    self.sayWord(obj)
             else:
                 [startOffset, endOffset] = self.getOffsetsForChar(obj)
                 self.sayCharacter(obj)
@@ -3228,7 +3338,7 @@
             startOffset = text.caretOffset
             endOffset = text.caretOffset
 
-        self._saveLastCursorPosition(text)
+        self._saveLastCursorPosition(obj, text.caretOffset)
         self._saveSpokenTextRange(startOffset, endOffset)
 
     def onCaretMoved(self, event):
@@ -3652,7 +3762,8 @@
         """
 
         text = obj.queryText()
-        startOffset = self.pointOfReference["lastCursorPosition"]
+        lastPos = self.pointOfReference.get("lastCursorPosition")
+        startOffset = lastPos[1]
         endOffset = text.caretOffset
 
         # Swap values if in wrong order (StarOffice is fussy about that).
@@ -3716,13 +3827,37 @@
         - event: the Event
         """
 
-        startOffset = 0
-        endOffset = 0
-        if "spokenTextRange" in self.pointOfReference:
-            startOffset = self.pointOfReference["spokenTextRange"][0]
-            endOffset = self.pointOfReference["spokenTextRange"][1]
-        self.speakTextSelectionState(event.source, startOffset, endOffset)
+        obj = event.source
+        spokenRange = self.pointOfReference.get("spokenTextRange") or [0, 0]
+        startOffset, endOffset = spokenRange
 
+        if not obj.getState().contains(pyatspi.STATE_FOCUSED):
+            # If we are selecting text across object boundaries with
+            # Shift+Down and doing so from the very beginning of the
+            # line, we will get an event telling us that the selection
+            # has changed for the line we just left.  Because nothing
+            # is selected on the line which contains the caret, we won't
+            # speak "selected."  So check to see if the event is for the
+            # object we just left and be sure that we don't have a spoken
+            # range (otherwise, we'll say selected twice when we are
+            # selecting across boundaries from the middle of the line).
+            #
+            lastPos = self.pointOfReference.get("lastCursorPosition")
+            if not lastPos or spokenRange != [0, 0]:
+                return
+
+            prevObj = None
+            for relation in lastPos[0].getRelationSet():
+                if relation.getRelationType() == pyatspi.RELATION_FLOWS_FROM:
+                    prevObj = relation.getTarget(0)
+
+            if not self.isSameObject(prevObj, obj):
+                return
+
+            endOffset = obj.queryText().characterCount
+
+        self.speakTextSelectionState(obj, startOffset, endOffset)    
+            
     def onSelectionChanged(self, event):
         """Called when an object's selection changes.
 
@@ -6776,14 +6911,15 @@
 
         self.pointOfReference["spokenTextRange"] = [startOffset, endOffset]
 
-    def _saveLastCursorPosition(self, text):
+    def _saveLastCursorPosition(self, obj, caretOffset):
         """Save away the current text cursor position for next time.
 
         Arguments:
-        - text: the text object.
+        - obj: the current accessible
+        - caretOffset: the cursor position within this object
         """
 
-        self.pointOfReference["lastCursorPosition"] = text.caretOffset
+        self.pointOfReference["lastCursorPosition"] = [obj, caretOffset]
 
     def _saveLastTextSelections(self, text):
         """Save away the list of text selections for next time.
@@ -6943,6 +7079,8 @@
         if specialCaseFound:
             speech.speak(line, None, False)
             return
+        elif startOffset == endOffset:
+            return
 
         try:
             # If we are selecting by word, then there possibly will be
@@ -6973,6 +7111,7 @@
                         startOffset += 1
                     else:
                         break
+
         except:
             debug.printException(debug.LEVEL_FINEST)
 
@@ -6983,23 +7122,14 @@
             # ONLY TRANSLATE THE PART AFTER THE PIPE CHARACTER |
             #
             speech.speak(Q_("text|selected"), None, False)
-        else:
-            if self.pointOfReference.has_key("lastSelections"):
-                for i in xrange(len(self.pointOfReference["lastSelections"])):
-                    startSelOffset = \
-                        self.pointOfReference["lastSelections"][0][0]
-                    endSelOffset = \
-                        self.pointOfReference["lastSelections"][0][1]
-                    if (startOffset >= startSelOffset) \
-                        and (endOffset <= endSelOffset):
-                        # Translators: when the user unselects
-                        # (unhighlights) text in a document, Orca lets
-                        # them know this.
-                        #
-                        # ONLY TRANSLATE THE PART AFTER THE PIPE CHARACTER |
-                        #
-                        speech.speak(Q_("text|unselected"), None, False)
-                        break
+        elif len(text.getText(startOffset, endOffset)):
+            # Translators: when the user unselects
+            # (unhighlights) text in a document, Orca lets
+            # them know this.
+            #
+            # ONLY TRANSLATE THE PART AFTER THE PIPE CHARACTER |
+            #
+            speech.speak(Q_("text|unselected"), None, False)
 
         self._saveLastTextSelections(text)
 
Index: src/orca/where_am_I.py
===================================================================
--- src/orca/where_am_I.py	(revision 3904)
+++ src/orca/where_am_I.py	(working copy)
@@ -1267,7 +1267,7 @@
 
         return text
 
-    def _getTextSelection(self, obj):
+    def getTextSelection(self, obj):
         """Get the text selection for the given object.
 
         Arguments:
@@ -1284,12 +1284,12 @@
             [startOffset, endOffset] = textObj.getSelection(i)
 
             debug.println(self._debugLevel,
-                "_getTextSelection: selection start=%d, end=%d" % \
+                "getTextSelection: selection start=%d, end=%d" % \
                 (startOffset, endOffset))
 
             selectedText = textObj.getText(startOffset, endOffset)
             debug.println(self._debugLevel,
-                "_getTextSelection: selected text=<%s>" % selectedText)
+                "getTextSelection: selected text=<%s>" % selectedText)
 
             if i > 0:
                 textContents += " "
@@ -1317,7 +1317,7 @@
         text = obj.queryText()
         if text.getNSelections() > 0:
             [textContents, startOffset, endOffset] = \
-                                            self._getTextSelection(obj)
+                                            self.getTextSelection(obj)
 
         if not basicOnly:
             current = obj
@@ -1331,7 +1331,7 @@
                         prevObjText = prevObj.queryText()
                         if prevObjText.getNSelections() > 0:
                             [newTextContents, start, end] = \
-                                         self._getTextSelection(prevObj)
+                                         self.getTextSelection(prevObj)
                             textContents = newTextContents + " " + textContents
                             current = prevObj
                             morePossibleSelections = True
@@ -1353,7 +1353,7 @@
                         nextObjText = nextObj.queryText()
                         if nextObjText.getNSelections() > 0:
                             [newTextContents, start, end] = \
-                                         self._getTextSelection(nextObj)
+                                         self.getTextSelection(nextObj)
                             textContents += " " + newTextContents
                             current = nextObj
                             morePossibleSelections = True
