Skip to content

Commit

Permalink
Revert "Revert "Introducing table sayall commands (nvaccess#13670)" (n…
Browse files Browse the repository at this point in the history
…vaccess#13954)"

This reverts commit 9d25bac.
  • Loading branch information
mltony committed Aug 25, 2022
1 parent c5a7e68 commit a174193
Show file tree
Hide file tree
Showing 8 changed files with 557 additions and 161 deletions.
302 changes: 210 additions & 92 deletions source/documentBase.py

Large diffs are not rendered by default.

159 changes: 119 additions & 40 deletions source/speech/sayAll.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
# Copyright (C) 2006-2022 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Babbage B.V., Bill Dengler,
# Julien Cochuyt

from abc import ABCMeta, abstractmethod
from enum import IntEnum
from typing import Callable, TYPE_CHECKING
from typing import Callable, TYPE_CHECKING, Optional
import weakref
import garbageHandler
from logHandler import log
Expand Down Expand Up @@ -36,6 +37,7 @@
class CURSOR(IntEnum):
CARET = 0
REVIEW = 1
TABLE = 2


SayAllHandler = None
Expand Down Expand Up @@ -95,10 +97,23 @@ def readObjects(self, obj: 'NVDAObjects.NVDAObject'):
self._getActiveSayAll = weakref.ref(reader)
reader.next()

def readText(self, cursor: CURSOR):
def readText(
self,
cursor: CURSOR,
startPos: Optional[textInfos.TextInfo] = None,
nextLineFunc: Optional[Callable[[textInfos.TextInfo], textInfos.TextInfo]] = None,
shouldUpdateCaret: bool = True,
) -> None:
self.lastSayAllMode = cursor
try:
reader = _TextReader(self, cursor)
if cursor == CURSOR.CARET:
reader = _CaretTextReader(self)
elif cursor == CURSOR.REVIEW:
reader = _ReviewTextReader(self)
elif cursor == CURSOR.TABLE:
reader = _TableTextReader(self, startPos, nextLineFunc, shouldUpdateCaret)
else:
raise RuntimeError(f"Unknown cursor {cursor}")
except NotImplementedError:
log.debugWarning("Unable to make reader", exc_info=True)
return
Expand Down Expand Up @@ -149,7 +164,7 @@ def stop(self):
self.walker = None


class _TextReader(garbageHandler.TrackedObject):
class _TextReader(garbageHandler.TrackedObject, metaclass=ABCMeta):
"""Manages continuous reading of text.
This is intended for internal use only.
Expand All @@ -171,35 +186,41 @@ class _TextReader(garbageHandler.TrackedObject):
"""
MAX_BUFFERED_LINES = 10

def __init__(self, handler: _SayAllHandler, cursor: CURSOR):
def __init__(self, handler: _SayAllHandler):
self.handler = handler
self.cursor = cursor
self.trigger = SayAllProfileTrigger()
self.reader = None
# Start at the cursor.
if cursor == CURSOR.CARET:
try:
self.reader = api.getCaretObject().makeTextInfo(textInfos.POSITION_CARET)
except (NotImplementedError, RuntimeError) as e:
raise NotImplementedError("Unable to make TextInfo: " + str(e))
else:
self.reader = api.getReviewPosition()
self.reader = self.getInitialTextInfo()
# #10899: SayAll profile can't be activated earlier because they may not be anything to read
self.trigger.enter()
self.speakTextInfoState = SayAllHandler._makeSpeakTextInfoState(self.reader.obj)
self.numBufferedLines = 0
self.initialIteration = True

def nextLine(self):
if not self.reader:
log.debug("no self.reader")
# We were stopped.
return
if not self.reader.obj:
log.debug("no self.reader.obj")
# The object died, so we should too.
self.finish()
return
bookmark = self.reader.bookmark
@abstractmethod
def getInitialTextInfo(self) -> textInfos.TextInfo:
...

@abstractmethod
def updateCaret(self, updater: textInfos.TextInfo) -> None:
...

def shouldReadInitialPosition(self) -> bool:
return False

def nextLineImpl(self) -> bool:
"""
Advances cursor to the next reading chunk (e.g. paragraph).
@return: C{True} if advanced successfully, C{False} otherwise.
"""
# Collapse to the end of this line, ready to read the next.
try:
self.reader.collapse(end=True)
except RuntimeError:
# This occurs in Microsoft Word when the range covers the end of the document.
# without this exception to indicate that further collapsing is not possible,
# say all could enter an infinite loop.

return False
# Expand to the current line.
# We use move end rather than expand
# because the user might start in the middle of a line
Expand All @@ -215,8 +236,25 @@ def nextLine(self):
self.handler.speechWithoutPausesInstance.speakWithoutPauses([cb, EndUtteranceCommand()])
else:
self.finish()
return
return False
return True

def nextLine(self):
if not self.reader:
log.debug("no self.reader")
# We were stopped.
return
if not self.reader.obj:
log.debug("no self.reader.obj")
# The object died, so we should too.
self.finish()
return
if not self.initialIteration or not self.shouldReadInitialPosition():
if not self.nextLineImpl():
self.finish()
return
self.initialIteration = False
bookmark = self.reader.bookmark
# Copy the speakTextInfoState so that speak callbackCommand
# and its associated callback are using a copy isolated to this specific line.
state = self.speakTextInfoState.copy()
Expand Down Expand Up @@ -248,15 +286,6 @@ def _onLineReached(obj=self.reader.obj, state=state):
# Update the textInfo state ready for when speaking the next line.
self.speakTextInfoState = state.copy()

# Collapse to the end of this line, ready to read the next.
try:
self.reader.collapse(end=True)
except RuntimeError:
# This occurs in Microsoft Word when the range covers the end of the document.
# without this exception to indicate that further collapsing is not possible,
# say all could enter an infinite loop.
self.finish()
return
if not spoke:
# This line didn't include a natural pause, so nothing was spoken.
self.numBufferedLines += 1
Expand All @@ -275,10 +304,7 @@ def lineReached(self, obj, bookmark, state):
# We've just started speaking this line, so move the cursor there.
state.updateObj()
updater = obj.makeTextInfo(bookmark)
if self.cursor == CURSOR.CARET:
updater.updateCaret()
if self.cursor != CURSOR.CARET or config.conf["reviewCursor"]["followCaret"]:
api.setReviewPosition(updater, isCaret=self.cursor == CURSOR.CARET)
self.updateCaret(updater)
winKernel.SetThreadExecutionState(winKernel.ES_SYSTEM_REQUIRED)
if self.numBufferedLines == 0:
# This was the last line spoken, so move on.
Expand Down Expand Up @@ -320,6 +346,59 @@ def stop(self):
def __del__(self):
self.stop()


class _CaretTextReader(_TextReader):
def getInitialTextInfo(self) -> textInfos.TextInfo:
try:
return api.getCaretObject().makeTextInfo(textInfos.POSITION_CARET)
except (NotImplementedError, RuntimeError) as e:
raise NotImplementedError("Unable to make TextInfo: ", e)

def updateCaret(self, updater: textInfos.TextInfo) -> None:
updater.updateCaret()
if config.conf["reviewCursor"]["followCaret"]:
api.setReviewPosition(updater, isCaret=True)


class _ReviewTextReader(_TextReader):
def getInitialTextInfo(self) -> textInfos.TextInfo:
return api.getReviewPosition()

def updateCaret(self, updater: textInfos.TextInfo) -> None:
api.setReviewPosition(updater, isCaret=False)


class _TableTextReader(_CaretTextReader):
def __init__(
self,
handler: _SayAllHandler,
startPos: Optional[textInfos.TextInfo] = None,
nextLineFunc: Optional[Callable[[textInfos.TextInfo], textInfos.TextInfo]] = None,
shouldUpdateCaret: bool = True,
):
self.startPos = startPos
self.nextLineFunc = nextLineFunc
self.shouldUpdateCaret = shouldUpdateCaret
super().__init__(handler)

def getInitialTextInfo(self) -> textInfos.TextInfo:
return self.startPos or super().getInitialTextInfo()

def nextLineImpl(self) -> bool:
try:
self.reader = self.nextLineFunc(self.reader)
return True
except StopIteration:
return False

def shouldReadInitialPosition(self) -> bool:
return True

def updateCaret(self, updater: textInfos.TextInfo) -> None:
if self.shouldUpdateCaret:
return super().updateCaret(updater)


class SayAllProfileTrigger(config.ProfileTrigger):
"""A configuration profile trigger for when say all is in progress.
"""
Expand Down
19 changes: 10 additions & 9 deletions source/virtualBuffers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import treeInterceptorHandler
import watchdog
from abc import abstractmethod
import documentBase


VBufStorage_findDirection_forward=0
VBufStorage_findDirection_back=1
Expand Down Expand Up @@ -640,15 +642,14 @@ def _iterTableCells(self, tableID, startPos=None, direction="next", row=None, co

def _getNearestTableCell(
self,
tableID,
startPos,
origRow,
origCol,
origRowSpan,
origColSpan,
movement,
axis
):
startPos: textInfos.TextInfo,
cell: documentBase._TableCell,
movement: documentBase._Movement,
axis: documentBase._Axis,
) -> textInfos.TextInfo:
tableID, origRow, origCol, origRowSpan, origColSpan = (
cell.tableID, cell.row, cell.col, cell.rowSpan, cell.colSpan
)
# Determine destination row and column.
destRow = origRow
destCol = origCol
Expand Down
26 changes: 7 additions & 19 deletions source/virtualBuffers/gecko_ia2.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import aria
import config
from NVDAObjects.IAccessible import normalizeIA2TextFormatField, IA2TextTextInfo
import documentBase


def _getNormalizedCurrentAttrs(attrs: textInfos.ControlField) -> typing.Dict[str, typing.Any]:
Expand Down Expand Up @@ -548,26 +549,13 @@ def _getTableCellAt(self,tableID,startPos,destRow,destCol):

def _getNearestTableCell(
self,
tableID,
startPos,
origRow,
origCol,
origRowSpan,
origColSpan,
movement,
axis,
):
startPos: textInfos.TextInfo,
cell: documentBase._TableCell,
movement: documentBase._Movement,
axis: documentBase._Axis,
) -> textInfos.TextInfo:
# Skip the VirtualBuffer implementation as the base BrowseMode implementation is good enough for us here.
return super(VirtualBuffer, self)._getNearestTableCell(
tableID,
startPos,
origRow,
origCol,
origRowSpan,
origColSpan,
movement,
axis
)
return super(VirtualBuffer, self)._getNearestTableCell(startPos, cell, movement, axis)

def _get_documentConstantIdentifier(self):
try:
Expand Down

0 comments on commit a174193

Please sign in to comment.