44# Copyright (C) 2006-2021 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Babbage B.V., Bill Dengler,
55# Julien Cochuyt
66
7- from abc import ABCMeta , abstractmethod
87from enum import IntEnum
9- from typing import Callable , TYPE_CHECKING , Optional
8+ from typing import Callable , TYPE_CHECKING
109import weakref
1110import garbageHandler
1211from logHandler import log
3736class CURSOR (IntEnum ):
3837 CARET = 0
3938 REVIEW = 1
40- TABLE = 2
4139
4240
4341SayAllHandler = None
@@ -97,23 +95,10 @@ def readObjects(self, obj: 'NVDAObjects.NVDAObject'):
9795 self ._getActiveSayAll = weakref .ref (reader )
9896 reader .next ()
9997
100- def readText (
101- self ,
102- cursor : CURSOR ,
103- startPos : Optional [textInfos .TextInfo ] = None ,
104- nextLineFunc : Optional [Callable [[textInfos .TextInfo ], textInfos .TextInfo ]] = None ,
105- shouldUpdateCaret : bool = True ,
106- ) -> None :
98+ def readText (self , cursor : CURSOR ):
10799 self .lastSayAllMode = cursor
108100 try :
109- if cursor == CURSOR .CARET :
110- reader = _CaretTextReader (self )
111- elif cursor == CURSOR .REVIEW :
112- reader = _ReviewTextReader (self )
113- elif cursor == CURSOR .TABLE :
114- reader = _TableTextReader (self , startPos , nextLineFunc , shouldUpdateCaret )
115- else :
116- raise RuntimeError (f"Unknown cursor { cursor } " )
101+ reader = _TextReader (self , cursor )
117102 except NotImplementedError :
118103 log .debugWarning ("Unable to make reader" , exc_info = True )
119104 return
@@ -160,7 +145,7 @@ def stop(self):
160145 self .walker = None
161146
162147
163- class _TextReader (garbageHandler .TrackedObject , metaclass = ABCMeta ):
148+ class _TextReader (garbageHandler .TrackedObject ):
164149 """Manages continuous reading of text.
165150 This is intended for internal use only.
166151
@@ -182,41 +167,35 @@ class _TextReader(garbageHandler.TrackedObject, metaclass=ABCMeta):
182167 """
183168 MAX_BUFFERED_LINES = 10
184169
185- def __init__ (self , handler : _SayAllHandler ):
170+ def __init__ (self , handler : _SayAllHandler , cursor : CURSOR ):
186171 self .handler = handler
172+ self .cursor = cursor
187173 self .trigger = SayAllProfileTrigger ()
188- self .reader = self .getInitialTextInfo ()
174+ self .reader = None
175+ # Start at the cursor.
176+ if cursor == CURSOR .CARET :
177+ try :
178+ self .reader = api .getCaretObject ().makeTextInfo (textInfos .POSITION_CARET )
179+ except (NotImplementedError , RuntimeError ) as e :
180+ raise NotImplementedError ("Unable to make TextInfo: " + str (e ))
181+ else :
182+ self .reader = api .getReviewPosition ()
189183 # #10899: SayAll profile can't be activated earlier because they may not be anything to read
190184 self .trigger .enter ()
191185 self .speakTextInfoState = SayAllHandler ._makeSpeakTextInfoState (self .reader .obj )
192186 self .numBufferedLines = 0
193- self .initialIteration = True
194-
195- @abstractmethod
196- def getInitialTextInfo (self ) -> textInfos .TextInfo :
197- ...
198-
199- @abstractmethod
200- def updateCaret (self , updater : textInfos .TextInfo ) -> None :
201- ...
202-
203- def shouldReadInitialPosition (self ) -> bool :
204- return False
205-
206- def nextLineImpl (self ) -> bool :
207- """
208- Advances cursor to the next reading chunk (e.g. paragraph).
209- @return: C{True} if advanced successfully, C{False} otherwise.
210- """
211- # Collapse to the end of this line, ready to read the next.
212- try :
213- self .reader .collapse (end = True )
214- except RuntimeError :
215- # This occurs in Microsoft Word when the range covers the end of the document.
216- # without this exception to indicate that further collapsing is not possible,
217- # say all could enter an infinite loop.
218187
219- return False
188+ def nextLine (self ):
189+ if not self .reader :
190+ log .debug ("no self.reader" )
191+ # We were stopped.
192+ return
193+ if not self .reader .obj :
194+ log .debug ("no self.reader.obj" )
195+ # The object died, so we should too.
196+ self .finish ()
197+ return
198+ bookmark = self .reader .bookmark
220199 # Expand to the current line.
221200 # We use move end rather than expand
222201 # because the user might start in the middle of a line
@@ -232,25 +211,8 @@ def nextLineImpl(self) -> bool:
232211 self .handler .speechWithoutPausesInstance .speakWithoutPauses ([cb , EndUtteranceCommand ()])
233212 else :
234213 self .finish ()
235- return False
236- return True
237-
238- def nextLine (self ):
239- if not self .reader :
240- log .debug ("no self.reader" )
241- # We were stopped.
242- return
243- if not self .reader .obj :
244- log .debug ("no self.reader.obj" )
245- # The object died, so we should too.
246- self .finish ()
247214 return
248- if not self .initialIteration or not self .shouldReadInitialPosition ():
249- if not self .nextLineImpl ():
250- self .finish ()
251- return
252- self .initialIteration = False
253- bookmark = self .reader .bookmark
215+
254216 # Copy the speakTextInfoState so that speak callbackCommand
255217 # and its associated callback are using a copy isolated to this specific line.
256218 state = self .speakTextInfoState .copy ()
@@ -282,6 +244,14 @@ def _onLineReached(obj=self.reader.obj, state=state):
282244 # Update the textInfo state ready for when speaking the next line.
283245 self .speakTextInfoState = state .copy ()
284246
247+ # Collapse to the end of this line, ready to read the next.
248+ try :
249+ self .reader .collapse (end = True )
250+ except RuntimeError :
251+ # This occurs in Microsoft Word when the range covers the end of the document.
252+ # without this exception to indicate that further collapsing is not possible, say all could enter an infinite loop.
253+ self .finish ()
254+ return
285255 if not spoke :
286256 # This line didn't include a natural pause, so nothing was spoken.
287257 self .numBufferedLines += 1
@@ -300,7 +270,10 @@ def lineReached(self, obj, bookmark, state):
300270 # We've just started speaking this line, so move the cursor there.
301271 state .updateObj ()
302272 updater = obj .makeTextInfo (bookmark )
303- self .updateCaret (updater )
273+ if self .cursor == CURSOR .CARET :
274+ updater .updateCaret ()
275+ if self .cursor != CURSOR .CARET or config .conf ["reviewCursor" ]["followCaret" ]:
276+ api .setReviewPosition (updater , isCaret = self .cursor == CURSOR .CARET )
304277 winKernel .SetThreadExecutionState (winKernel .ES_SYSTEM_REQUIRED )
305278 if self .numBufferedLines == 0 :
306279 # This was the last line spoken, so move on.
@@ -342,59 +315,6 @@ def stop(self):
342315 def __del__ (self ):
343316 self .stop ()
344317
345-
346- class _CaretTextReader (_TextReader ):
347- def getInitialTextInfo (self ) -> textInfos .TextInfo :
348- try :
349- return api .getCaretObject ().makeTextInfo (textInfos .POSITION_CARET )
350- except (NotImplementedError , RuntimeError ) as e :
351- raise NotImplementedError ("Unable to make TextInfo: " , e )
352-
353- def updateCaret (self , updater : textInfos .TextInfo ) -> None :
354- updater .updateCaret ()
355- if config .conf ["reviewCursor" ]["followCaret" ]:
356- api .setReviewPosition (updater , isCaret = True )
357-
358-
359- class _ReviewTextReader (_TextReader ):
360- def getInitialTextInfo (self ) -> textInfos .TextInfo :
361- return api .getReviewPosition ()
362-
363- def updateCaret (self , updater : textInfos .TextInfo ) -> None :
364- api .setReviewPosition (updater , isCaret = False )
365-
366-
367- class _TableTextReader (_CaretTextReader ):
368- def __init__ (
369- self ,
370- handler : _SayAllHandler ,
371- startPos : Optional [textInfos .TextInfo ] = None ,
372- nextLineFunc : Optional [Callable [[textInfos .TextInfo ], textInfos .TextInfo ]] = None ,
373- shouldUpdateCaret : bool = True ,
374- ):
375- self .startPos = startPos
376- self .nextLineFunc = nextLineFunc
377- self .shouldUpdateCaret = shouldUpdateCaret
378- super ().__init__ (handler )
379-
380- def getInitialTextInfo (self ) -> textInfos .TextInfo :
381- return self .startPos or super ().getInitialTextInfo ()
382-
383- def nextLineImpl (self ) -> bool :
384- try :
385- self .reader = self .nextLineFunc (self .reader )
386- return True
387- except StopIteration :
388- return False
389-
390- def shouldReadInitialPosition (self ) -> bool :
391- return True
392-
393- def updateCaret (self , updater : textInfos .TextInfo ) -> None :
394- if self .shouldUpdateCaret :
395- return super ().updateCaret (updater )
396-
397-
398318class SayAllProfileTrigger (config .ProfileTrigger ):
399319 """A configuration profile trigger for when say all is in progress.
400320 """
0 commit comments