diff --git a/source/core.py b/source/core.py index 3f5de929186..3304d197ae6 100644 --- a/source/core.py +++ b/source/core.py @@ -351,9 +351,6 @@ def main(): import speech log.debug("Initializing speech") speech.initialize() - from speech import sayAll - log.debug("Initializing sayAllHandler") - sayAll.initialize() if not globalVars.appArgs.minimal and (time.time()-globalVars.startTime)>5: log.debugWarning("Slow starting core (%.2f sec)" % (time.time()-globalVars.startTime)) # Translators: This is spoken when NVDA is starting. diff --git a/source/documentBase.py b/source/documentBase.py index 84e949e26e4..c112cc8998a 100644 --- a/source/documentBase.py +++ b/source/documentBase.py @@ -4,13 +4,11 @@ #See the file COPYING for more details. from baseObject import AutoPropertyObject, ScriptableObject -from scriptHandler import isScriptWaiting import config import textInfos -import speech -import ui import controlTypes + class TextContainerObject(AutoPropertyObject): """ An object that contains text which can be accessed via a call to a makeTextInfo method. @@ -141,6 +139,12 @@ def _getNearestTableCell(self, tableID, startPos, origRow, origCol, origRowSpan, raise LookupError def _tableMovementScriptHelper(self, movement="next", axis=None): + # documentBase is a core module and should not depend on these UI modules and so they are imported + # at run-time. (#12404) + from scriptHandler import isScriptWaiting + from speech import speakTextInfo + import ui + if isScriptWaiting(): return formatConfig=config.conf["documentFormatting"].copy() @@ -162,7 +166,7 @@ def _tableMovementScriptHelper(self, movement="next", axis=None): # Retrieve the cell on which we started. info = self._getTableCellAt(tableID, self.selection,origRow, origCol) - speech.speakTextInfo(info, formatConfig=formatConfig, reason=controlTypes.OutputReason.CARET) + speakTextInfo(info, formatConfig=formatConfig, reason=controlTypes.OutputReason.CARET) info.collapse() self.selection = info @@ -187,6 +191,8 @@ def script_previousColumn(self, gesture): script_previousColumn.__doc__ = _("moves to the previous table column") def script_toggleIncludeLayoutTables(self,gesture): + # documentBase is a core module and should not depend on UI, so it is imported at run-time. (#12404) + import ui if config.conf["documentFormatting"]["includeLayoutTables"]: # Translators: The message announced when toggling the include layout tables browse mode setting. state = _("layout tables off") @@ -205,5 +211,3 @@ def script_toggleIncludeLayoutTables(self,gesture): "kb:control+alt+rightArrow": "nextColumn", "kb:control+alt+leftArrow": "previousColumn", } - - diff --git a/source/scriptHandler.py b/source/scriptHandler.py index dc88adb574a..517ee6ee384 100644 --- a/source/scriptHandler.py +++ b/source/scriptHandler.py @@ -6,12 +6,9 @@ from typing import List, Optional import time import weakref -import inspect import types import config -import speech from speech import sayAll -import appModuleHandler import api import queueHandler from logHandler import log @@ -19,7 +16,6 @@ import globalPluginHandler import braille import vision -import keyLabels import baseObject _numScriptsQueued=0 #Number of scripts that are queued to be executed diff --git a/source/speech/__init__.py b/source/speech/__init__.py index 2f91aabcffe..05bbf0f4132 100644 --- a/source/speech/__init__.py +++ b/source/speech/__init__.py @@ -55,6 +55,7 @@ speakSpelling, speakText, speakTextInfo, + SpeakTextInfoState, speakTextSelected, speakTypedCharacters, SpeechMode, @@ -132,6 +133,7 @@ "speakSpelling", "speakText", "speakTextInfo", + "SpeakTextInfoState", "speakTextSelected", "speakTypedCharacters", "SpeechMode", @@ -142,13 +144,22 @@ import synthDriverHandler import config from .speech import initialize as speechInitialize +from .sayAll import initialize as sayAllInitialize def initialize(): - """Loads and sets the synth driver configured in nvda.ini.""" + """ Loads and sets the synth driver configured in nvda.ini. + Initializes the state of speech and initializes the sayAllHandler + """ synthDriverHandler.initialize() synthDriverHandler.setSynth(config.conf["speech"]["synth"]) speechInitialize() + sayAllInitialize( + speak, + speakObject, + getTextInfoSpeech, + SpeakTextInfoState, + ) def terminate(): diff --git a/source/speech/sayAll.py b/source/speech/sayAll.py index 03138ff5ffd..1714fdeca4f 100644 --- a/source/speech/sayAll.py +++ b/source/speech/sayAll.py @@ -5,15 +5,9 @@ # Julien Cochuyt from enum import IntEnum -from typing import TYPE_CHECKING +from typing import Callable, TYPE_CHECKING import weakref import garbageHandler -from .speech import ( - speak, - getTextInfoSpeech, - SpeakTextInfoState, - speakObject, -) from logHandler import log import config import controlTypes @@ -26,11 +20,17 @@ from .speechWithoutPauses import SpeechWithoutPauses from .types import ( + SpeechSequence, _flattenNestedSequences, ) if TYPE_CHECKING: import NVDAObjects + from .speech import ( + getTextInfoSpeech, + SpeakTextInfoState, + speakObject, + ) class CURSOR(IntEnum): @@ -41,18 +41,38 @@ class CURSOR(IntEnum): SayAllHandler = None -def initialize(): +def initialize( + speakFunc: Callable[[SpeechSequence], None], + speakObject: 'speakObject', + getTextInfoSpeech: 'getTextInfoSpeech', + SpeakTextInfoState: 'SpeakTextInfoState', +): + log.debug("Initializing sayAllHandler") global SayAllHandler - SayAllHandler = _SayAllHandler(SpeechWithoutPauses(speakFunc=speak)) + SayAllHandler = _SayAllHandler( + SpeechWithoutPauses(speakFunc=speakFunc), + speakObject, + getTextInfoSpeech, + SpeakTextInfoState, + ) class _SayAllHandler: - def __init__(self, speechWithoutPausesInstance: SpeechWithoutPauses): + def __init__( + self, + speechWithoutPausesInstance: SpeechWithoutPauses, + speakObject: 'speakObject', + getTextInfoSpeech: 'getTextInfoSpeech', + SpeakTextInfoState: 'SpeakTextInfoState', + ): self.lastSayAllMode = None self.speechWithoutPausesInstance = speechWithoutPausesInstance #: The active say all manager. #: This is a weakref because the manager should be allowed to die once say all is complete. self._getActiveSayAll = lambda: None # noqa: Return None when called like a dead weakref. + self._speakObject = speakObject + self._getTextInfoSpeech = getTextInfoSpeech + self._makeSpeakTextInfoState = SpeakTextInfoState def stop(self): ''' @@ -115,7 +135,11 @@ def next(self): return # Call this method again when we start speaking this object. callbackCommand = CallbackCommand(self.next, name="say-all:next") - speakObject(obj, reason=controlTypes.OutputReason.SAYALL, _prefixSpeechCommand=callbackCommand) + SayAllHandler._speakObject( + obj, + reason=controlTypes.OutputReason.SAYALL, + _prefixSpeechCommand=callbackCommand + ) def stop(self): self.walker = None @@ -158,7 +182,7 @@ def __init__(self, handler: _SayAllHandler, cursor: CURSOR): self.reader = api.getReviewPosition() # #10899: SayAll profile can't be activated earlier because they may not be anything to read self.trigger.enter() - self.speakTextInfoState = SpeakTextInfoState(self.reader.obj) + self.speakTextInfoState = SayAllHandler._makeSpeakTextInfoState(self.reader.obj) self.numBufferedLines = 0 def nextLine(self): @@ -207,7 +231,7 @@ def _onLineReached(obj=self.reader.obj, state=state): # and insert the lineReached callback at the very beginning of the sequence. # _linePrefix on speakTextInfo cannot be used here # As it would be inserted in the sequence after all initial control starts which is too late. - speechGen = getTextInfoSpeech( + speechGen = SayAllHandler._getTextInfoSpeech( self.reader, unit=textInfos.UNIT_READINGCHUNK, reason=controlTypes.OutputReason.SAYALL,