Index: src/orca/scripts/gedit.py
===================================================================
--- src/orca/scripts/gedit.py	(revision 2260)
+++ src/orca/scripts/gedit.py	(working copy)
@@ -32,8 +32,10 @@
 import orca.orca as orca
 import orca.orca_state as orca_state
 import orca.rolenames as rolenames
+import orca.settings as settings
 import orca.speech as speech
 import orca.speechgenerator as speechgenerator
+import orca.speechserver as speechserver
 
 from orca.orca_i18n import _
 
@@ -109,6 +111,119 @@
         """
         return SpeechGenerator(self)
 
+    def __sayAllProgressCallback(self, context, type):
+        """Provide feedback during the sayAll operation.
+        """
+
+        if type == speechserver.SayAllContext.PROGRESS:
+            #print "PROGRESS", context.utterance, context.currentOffset
+            pass
+        elif type == speechserver.SayAllContext.INTERRUPTED:
+            #print "INTERRUPTED", context.utterance, context.currentOffset
+            offset = context.currentOffset
+            for i in range(0, len(context.obj)):
+                obj = context.obj[i]
+                charCount = obj.text.characterCount
+                if offset > charCount:
+                    offset -= charCount
+                else:
+                    obj.text.setCaretOffset(offset)
+                    break
+        elif type == speechserver.SayAllContext.COMPLETED:
+            #print "COMPLETED", context.utterance, context.currentOffset
+            obj = context.obj[len(context.obj)-1]
+            obj.text.setCaretOffset(context.currentOffset)
+            orca.setLocusOfFocus(None, obj, False)
+
+    def textLines(self, obj):
+        """Creates a generator that can be used to iterate over each line
+        of a text object, starting at the caret offset.
+
+        Arguments:
+        - obj: an Accessible that has a text specialization
+
+        Returns an iterator that produces elements of the form:
+        [SayAllContext, acss], where SayAllContext has the text to be
+        spoken and acss is an ACSS instance for speaking the text.
+        """
+        if not obj:
+            return
+
+        text = obj.text
+        if not text:
+            return
+
+        length = text.characterCount
+        offset = text.caretOffset
+        string = ""
+        textObjs = []
+        textObjs.append(obj)
+        startOffset = obj.text.caretOffset
+
+        # Get the next line of text to read
+        #
+        done = False
+        while not done:
+            lastEndOffset = -1
+            while offset < length:
+                [str, start, end] = text.getTextAtOffset(offset,
+                          atspi.Accessibility.TEXT_BOUNDARY_SENTENCE_END)
+
+                if len(str) != 0:
+                    string += str
+
+                if len(str) == 0 or str[len(str)-1] in '.?!':
+                    endOffset = end
+
+                    string = self.adjustForRepeats(string)
+                    if string.isupper():
+                        voice = settings.voices[settings.UPPERCASE_VOICE]
+                    else:
+                        voice = settings.voices[settings.DEFAULT_VOICE]
+
+                    yield [speechserver.SayAllContext(textObjs, string,
+                                                      startOffset, endOffset),
+                           voice]
+                    string = ""
+                    startOffset = endOffset
+
+                if len(str) == 0:
+                    break
+                else:
+                    offset = end
+
+            moreLines = False
+            relations = obj.relations
+            for relation in relations:
+                if relation.getRelationType()  \
+                       == atspi.Accessibility.RELATION_FLOWS_TO:
+                    obj = atspi.Accessible.makeAccessible(relation.getTarget(0))
+
+                    text = obj.text
+                    if not text:
+                        return
+
+                    textObjs.append(obj)
+                    length = text.characterCount
+                    offset = 0
+                    moreLines = True
+                    break
+            if not moreLines:
+                done = True
+
+        # If there is anything left unspoken, speak it now.
+        #
+        if len(string) != 0:
+            string = self.adjustForRepeats(string)
+            if string.isupper():
+                voice = settings.voices[settings.UPPERCASE_VOICE]
+            else:
+                voice = settings.voices[settings.DEFAULT_VOICE]
+
+            yield [speechserver.SayAllContext(textObjs, string,
+                                              startOffset, endOffset),
+                   voice]
+
     def readMisspeltWord(self, event, panel):
         """Speak/braille the current misspelt word plus its context.
            The spell check dialog contains a "paragraph" which shows the
Index: src/orca/scripts/Evolution.py
===================================================================
--- src/orca/scripts/Evolution.py	(revision 2254)
+++ src/orca/scripts/Evolution.py	(working copy)
@@ -32,8 +32,10 @@
 import orca.keybindings as keybindings
 import orca.rolenames as rolenames
 import orca.braille as braille
+import orca.orca as orca
 import orca.orca_state as orca_state
 import orca.speech as speech
+import orca.speechserver as speechserver
 import orca.settings as settings
 import orca.chnames as chnames
 
@@ -289,26 +291,110 @@
 
         return hrs + ' ' + mins + ' ' + suffix
 
-    def readTextLines(self):
-        """This an iterator that produces elements of the form:
-        [SayAllContext, acss], where SayAllContext has the text to be
-        spoken and acss is an ACSS instance for speaking the text.  We
-        do this because of the way Evolution lays out its messages:
-        each paragraph tends to be in its own panel, each of which is
+    def textLines(self, obj):
+        """Creates a generator that can be used to iterate over each line
+        of a text object, starting at the caret offset.
+
+        We have to subclass this because Evolution lays out its messages
+        such that each paragraph is in its own panel, each of which is
         in a higher level panel.  So, we just traverse through the
         children.
+
+        Arguments:
+        - obj: an Accessible that has a text specialization
+
+        Returns an iterator that produces elements of the form:
+        [SayAllContext, acss], where SayAllContext has the text to be
+        spoken and acss is an ACSS instance for speaking the text.
         """
-        panel = orca_state.locusOfFocus.parent
-        htmlPanel = orca_state.locusOfFocus.parent.parent
+
+        if not obj:
+            return
+
+        text = obj.text
+        if not text:
+            return
+
+        panel = obj.parent
+        htmlPanel = panel.parent
         startIndex = panel.index
-        for i in range(startIndex, htmlPanel.childCount):
+        i = startIndex
+        total = htmlPanel.childCount
+        textObjs = []
+        startOffset = obj.text.caretOffset
+        offset = obj.text.caretOffset
+        string = ""
+        done = False
+
+        while not done:
             accPanel = htmlPanel.accessible.getChildAtIndex(i)
             panel = atspi.Accessible.makeAccessible(accPanel)
             accTextObj = panel.accessible.getChildAtIndex(0)
             textObj = atspi.Accessible.makeAccessible(accTextObj)
-            for [context, acss] in self.textLines(textObj):
-                yield [context, acss]
+            text = textObj.text
+            if not text:
+                return
+            textObjs.append(textObj)
+            length = text.characterCount
 
+            while offset <= length:
+                [str, start, end] = textObj.text.getTextAtOffset(offset,
+                               atspi.Accessibility.TEXT_BOUNDARY_SENTENCE_END)
+                if len(str) != 0:
+                    string += str
+
+                if len(str) == 0 or str[len(str)-1] in '.?!':
+                    endOffset = end
+
+                    string = self.adjustForRepeats(string)
+                    if string.isupper():
+                        voice = settings.voices[settings.UPPERCASE_VOICE]
+                    else:
+                        voice = settings.voices[settings.DEFAULT_VOICE]
+
+                    if not textObjs:
+                        textObjs.append(textObj)
+                    yield [speechserver.SayAllContext(textObjs, string,
+                                                      startOffset, endOffset),
+                           voice]
+                    textObjs = []
+                    string = ""
+                    startOffset = endOffset
+
+                if len(str) == 0 or end == length:
+                    break
+                else:
+                    offset = end
+
+            offset = 0
+            i += 1
+            if i == total:
+                done = True
+
+    def __sayAllProgressCallback(self, context, type):
+        """Provide feedback during the sayAll operation.
+        """
+
+        if type == speechserver.SayAllContext.PROGRESS:
+            #print "PROGRESS", context.utterance, context.currentOffset
+            pass
+        elif type == speechserver.SayAllContext.INTERRUPTED:
+            #print "INTERRUPTED", context.utterance, context.currentOffset
+            offset = context.currentOffset
+            for i in range(0, len(context.obj)):
+                obj = context.obj[i]
+                charCount = obj.text.characterCount
+                if offset > charCount:
+                    offset -= charCount
+                else:
+                    obj.text.setCaretOffset(offset)
+                    break
+        elif type == speechserver.SayAllContext.COMPLETED:
+            #print "COMPLETED", context.utterance, context.currentOffset
+            obj = context.obj[len(context.obj)-1]
+            obj.text.setCaretOffset(context.currentOffset)
+            orca.setLocusOfFocus(None, obj, False)
+
     def sayAll(self, inputEvent):
         """Speak all the text associated with the text object that has
            focus. We have to define our own method here because Evolution
@@ -322,7 +408,7 @@
 
         debug.println(self.debugLevel, "evolution.sayAll.")
         if orca_state.locusOfFocus and orca_state.locusOfFocus.text:
-            speech.sayAll(self.readTextLines(),
+            speech.sayAll(self.textLines(orca_state.locusOfFocus), 
                           self.__sayAllProgressCallback)
         else:
             default.Script.sayAll(self, inputEvent)
Index: src/orca/default.py
===================================================================
--- src/orca/default.py	(revision 2254)
+++ src/orca/default.py	(working copy)
@@ -4444,7 +4444,7 @@
             while offset < length:
                 [string, startOffset, endOffset] = text.getTextAtOffset(
                     offset,
-                    atspi.Accessibility.TEXT_BOUNDARY_LINE_START)
+                    atspi.Accessibility.TEXT_BOUNDARY_SENTENCE_END)
 
                 # [[[WDW - HACK: well...gnome-terminal sometimes wants to
                 # give us outrageous values back from getTextAtOffset
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 2254)
+++ ChangeLog	(working copy)
@@ -1,3 +1,19 @@
+2007-04-06  Rich Burridge <rich.burridge@sun.com>
+
+        * src/orca/default.py:
+          src/orca/scripts/gedit.py:
+          src/orca/scripts/Evolution.py:
+          Fix for bug #400716 – [requirement] sayAll should be done by 
+          sentences..
+          textLines() now uses TEXT_BOUNDARY_SENTENCE_END instead of
+          TEXT_BOUNDARY_LINE_START to break apart chunks to read (thanks
+          Will!)
+
+          Fix appears to work nicely for gedit, OOo Writer and Evolution. 
+
+          It still needs to be implemented for Firefox, but that's a
+          separate bug (http://bugzilla.gnome.org/show_bug.cgi?id=426360).
+
 2007-04-05  Willie Walker <william.walker@sun.com>
 
 	* src/orca/keynames.py, src/orca/orca.py, src/orca/keybindings.py,
