1313from threading import Thread
1414from urllib import request
1515
16+ import api
17+ import braille
18+ import controlTypes
1619import core
20+ import globalCommands
1721import globalPluginHandler
1822import gui
23+ import inputCore
1924import queueHandler
25+ import scriptHandler
26+ import speech
27+ import textInfos
28+ import treeInterceptorHandler
29+ import ui
2030import wx
2131from gui .message import DisplayableError
2232from logHandler import log
33+ from scriptHandler import script
2334from synthDriverHandler import findAndSetNextSynth , getSynth , synthChanged
35+ from utils .security import objectBelowLockScreenAndWindowsIsLocked
2436
2537from globalPlugins .hear2readng_global_plugin .english_settings import (
2638 EnglishSpeechSettingsDialog ,
3244from globalPlugins .hear2readng_global_plugin .voice_manager import (
3345 Hear2ReadNGVoiceManagerDialog ,
3446)
47+ from synthDrivers ._H2R_NG_Speak import getCurrentVoice
3548
3649# from .voice_manager import Hear2ReadNGVoiceManagerDialog
50+ SCRCAT_TEXTREVIEW = _ ("Text review" )
3751
52+ curr_synth_name = ""
3853
3954class GlobalPlugin (globalPluginHandler .GlobalPlugin ):
4055 def __init__ (self , * args , ** kwargs ):
56+ global curr_synth_name
4157 super ().__init__ (* args , ** kwargs )
4258 self .__voice_manager_shown = False
4359 curr_synth_name = getSynth ().name
@@ -70,9 +86,11 @@ def __init__(self, *args, **kwargs):
7086 self .eng_settings_active = True
7187
7288 synthChanged .register (self .on_synth_changed )
73-
89+
7490 def on_synth_changed (self , synth ):
75- if "Hear2Read NG" in synth .name :
91+ global curr_synth_name
92+ curr_synth_name = synth .name
93+ if "Hear2Read NG" in curr_synth_name :
7694 # self.eng_settings_id = wx.Window.NewControlId()
7795 self .make_eng_settings_menu ()
7896 self .eng_settings_active = True
@@ -219,3 +237,229 @@ def terminate(self):
219237 gui .mainFrame .sysTrayIcon .menu .DestroyItem (self .itemHandle )
220238 except :
221239 pass
240+
241+
242+ ############################################################################
243+ # Scripts to enable spelling clarification
244+ # These scripts override native NVDA gestures, and care has been taken that
245+ # the fuctionality isn't affected outside of the Hear2Read addon and TTS
246+ ############################################################################
247+
248+ @script (
249+ description = _ (
250+ # Translators: Input help mode message for report current character under review cursor command.
251+ "Reports the character of the current navigator object where the review cursor is situated. "
252+ "Pressing twice reports a description or example of that character. "
253+ "Pressing three times reports the numeric value of the character in decimal and hexadecimal" ,
254+ ),
255+ category = globalCommands .SCRCAT_TEXTREVIEW ,
256+ gestures = ("kb:numpad2" , "kb(laptop):NVDA+." ),
257+ speakOnDemand = True ,
258+ )
259+ def script_h2r_review_currentCharacter (self , gesture : inputCore .InputGesture ):
260+
261+ if "Hear2Read NG" not in curr_synth_name :
262+ globalCommands .commands .script_review_currentCharacter (gesture )
263+ return
264+
265+ info = api .getReviewPosition ().copy ()
266+ # This script is available on the lock screen via getSafeScripts, as such
267+ # ensure the review position does not contain secure information
268+ # before announcing this object
269+ if objectBelowLockScreenAndWindowsIsLocked (info .obj ):
270+ ui .reviewMessage (gui .blockAction .Context .WINDOWS_LOCKED .translatedMessage )
271+ return
272+
273+ info .expand (textInfos .UNIT_CHARACTER )
274+ scriptCount = scriptHandler .getLastScriptRepeatCount ()
275+ log .info (f"script_review_currentCharacter interrupt: { scriptCount } , info: { info .text } " )
276+
277+ if scriptCount == 1 :
278+ try :
279+ lang = getCurrentVoice ().split ("-" )[0 ]
280+ # Explicitly tether here
281+ braille .handler .handleReviewMove (shouldAutoTether = True )
282+ speech .speakSpelling (info .text , locale = lang , useCharacterDescriptions = True )
283+ except :
284+ globalCommands .commands .script_review_currentCharacter (gesture )
285+ else :
286+ globalCommands .commands .script_review_currentCharacter (gesture )
287+
288+
289+ @script (
290+ description = _ (
291+ # Translators: Input help mode message for report current word under review cursor command.
292+ "Speaks the word of the current navigator object where the review cursor is situated. "
293+ "Pressing twice spells the word. "
294+ "Pressing three times spells the word using character descriptions" ,
295+ ),
296+ category = globalCommands .SCRCAT_TEXTREVIEW ,
297+ gestures = ("kb:numpad5" , "kb(laptop):NVDA+control+." , "ts(text):hoverUp" ),
298+ speakOnDemand = True ,
299+ )
300+ def script_h2r_review_currentWord (self , gesture : inputCore .InputGesture ):
301+
302+ if "Hear2Read NG" not in curr_synth_name :
303+ globalCommands .commands .script_review_currentWord (gesture )
304+ return
305+
306+ info = api .getReviewPosition ().copy ()
307+ # This script is available on the lock screen via getSafeScripts, as such
308+ # ensure the review position does not contain secure information
309+ # before announcing this object
310+ if objectBelowLockScreenAndWindowsIsLocked (info .obj ):
311+ ui .reviewMessage (gui .blockAction .Context .WINDOWS_LOCKED .translatedMessage )
312+ return
313+
314+ info .expand (textInfos .UNIT_WORD )
315+ # Explicitly tether here
316+ braille .handler .handleReviewMove (shouldAutoTether = True )
317+ scriptCount = scriptHandler .getLastScriptRepeatCount ()
318+ if scriptCount == 0 :
319+ speech .speakTextInfo (info , reason = controlTypes .OutputReason .CARET , unit = textInfos .UNIT_WORD )
320+ elif scriptCount == 1 :
321+ speech .spellTextInfo (info , useCharacterDescriptions = False )
322+ else :
323+ try :
324+ lang = getCurrentVoice ().split ("-" )[0 ]
325+ speech .speakSpelling (info .text , locale = lang , useCharacterDescriptions = True )
326+ except :
327+ speech .speakSpelling (info .text , useCharacterDescriptions = True )
328+
329+ @script (
330+ description = _ (
331+ # Translators: Input help mode message for read current line under review cursor command.
332+ "Reports the line of the current navigator object where the review cursor is situated. "
333+ "If this key is pressed twice, the current line will be spelled. "
334+ "Pressing three times will spell the line using character descriptions." ,
335+ ),
336+ category = globalCommands .SCRCAT_TEXTREVIEW ,
337+ gestures = ("kb:numpad8" , "kb(laptop):NVDA+shift+." ),
338+ speakOnDemand = True ,
339+ )
340+ def script_h2r_review_currentLine (self , gesture : inputCore .InputGesture ):
341+
342+ if "Hear2Read NG" not in curr_synth_name :
343+ globalCommands .commands .script_review_currentLine (gesture )
344+ return
345+
346+ info = api .getReviewPosition ().copy ()
347+ # This script is available on the lock screen via getSafeScripts, as such
348+ # ensure the review position does not contain secure information
349+ # before announcing this object
350+ if objectBelowLockScreenAndWindowsIsLocked (info .obj ):
351+ ui .reviewMessage (gui .blockAction .Context .WINDOWS_LOCKED .translatedMessage )
352+ return
353+ info .expand (textInfos .UNIT_LINE )
354+ # Explicitly tether here
355+ braille .handler .handleReviewMove (shouldAutoTether = True )
356+ scriptCount = scriptHandler .getLastScriptRepeatCount ()
357+ if scriptCount == 0 :
358+ speech .speakTextInfo (info , unit = textInfos .UNIT_LINE , reason = controlTypes .OutputReason .CARET )
359+ elif scriptCount == 1 :
360+ speech .spellTextInfo (info , useCharacterDescriptions = False )
361+ else :
362+ try :
363+ lang = getCurrentVoice ().split ("-" )[0 ]
364+ speech .speakSpelling (info .text , locale = lang , useCharacterDescriptions = True )
365+ except :
366+ speech .speakSpelling (info .text , useCharacterDescriptions = True )
367+
368+ @script (
369+ description = _ (
370+ # Translators: Input help mode message for report current line command.
371+ "Reports the current line under the application cursor. "
372+ "Pressing this key twice will spell the current line. "
373+ "Pressing three times will spell the line using character descriptions." ,
374+ ),
375+ category = globalCommands .SCRCAT_SYSTEMCARET ,
376+ gestures = ("kb(desktop):NVDA+upArrow" , "kb(laptop):NVDA+l" ),
377+ speakOnDemand = True ,
378+ )
379+ def script_h2r_reportCurrentLine (self , gesture ):
380+ if "Hear2Read NG" not in curr_synth_name :
381+ globalCommands .commands .script_reportCurrentLine (gesture )
382+ return
383+
384+ obj = api .getFocusObject ()
385+ treeInterceptor = obj .treeInterceptor
386+ if (
387+ isinstance (treeInterceptor , treeInterceptorHandler .DocumentTreeInterceptor )
388+ and not treeInterceptor .passThrough
389+ ):
390+ obj = treeInterceptor
391+ try :
392+ info = obj .makeTextInfo (textInfos .POSITION_CARET )
393+ except (NotImplementedError , RuntimeError ):
394+ info = obj .makeTextInfo (textInfos .POSITION_FIRST )
395+ info .expand (textInfos .UNIT_LINE )
396+ scriptCount = scriptHandler .getLastScriptRepeatCount ()
397+ if scriptCount == 0 :
398+ speech .speakTextInfo (info , unit = textInfos .UNIT_LINE , reason = controlTypes .OutputReason .CARET )
399+ elif scriptCount == 1 :
400+ speech .spellTextInfo (info , useCharacterDescriptions = False )
401+ else :
402+ try :
403+ lang = getCurrentVoice ().split ("-" )[0 ]
404+ speech .speakSpelling (info .text , locale = lang , useCharacterDescriptions = True )
405+ except :
406+ speech .speakSpelling (info .text , useCharacterDescriptions = True )
407+
408+ @script (
409+ description = _ (
410+ # Translators: Input help mode message for report current selection command.
411+ "Announces the current selection in edit controls and documents. "
412+ "Pressing twice spells this information. "
413+ "Pressing three times spells it using character descriptions. "
414+ "Pressing four times shows it in a browsable message. " ,
415+ ),
416+ category = globalCommands .SCRCAT_SYSTEMCARET ,
417+ gestures = ("kb(desktop):NVDA+shift+upArrow" , "kb(laptop):NVDA+shift+s" ),
418+ speakOnDemand = True ,
419+ )
420+ def script_h2r_reportCurrentSelection (self , gesture ):
421+ if "Hear2Read NG" not in curr_synth_name :
422+ globalCommands .commands .script_reportCurrentSelection (gesture )
423+ return
424+
425+ obj = api .getFocusObject ()
426+ treeInterceptor = obj .treeInterceptor
427+ if (
428+ isinstance (treeInterceptor , treeInterceptorHandler .DocumentTreeInterceptor )
429+ and not treeInterceptor .passThrough
430+ ):
431+ obj = treeInterceptor
432+ try :
433+ info = obj .makeTextInfo (textInfos .POSITION_SELECTION )
434+ except (RuntimeError , NotImplementedError ):
435+ info = None
436+ if not info or info .isCollapsed :
437+ # Translators: The message reported when there is no selection
438+ ui .message (_ ("No selection" ))
439+ else :
440+ scriptCount = scriptHandler .getLastScriptRepeatCount ()
441+ # Translators: The message reported after selected text
442+ selectMessage = speech .speech ._getSelectionMessageSpeech (_ ("%s selected" ), info .text )[0 ]
443+ if scriptCount == 0 :
444+ speech .speakTextSelected (info .text )
445+ braille .handler .message (selectMessage )
446+ elif scriptCount == 3 :
447+ ui .browseableMessage (info .text , copyButton = True , closeButton = True )
448+ return
449+
450+ elif len (info .text ) < speech .speech .MAX_LENGTH_FOR_SELECTION_REPORTING :
451+ if scriptCount == 1 :
452+ speech .speakSpelling (info .text , useCharacterDescriptions = False )
453+ else :
454+ try :
455+ lang = getCurrentVoice ().split ("-" )[0 ]
456+ speech .speakSpelling (info .text , locale = lang , useCharacterDescriptions = True )
457+ except :
458+ speech .speakSpelling (info .text , useCharacterDescriptions = True )
459+ else :
460+ speech .speakTextSelected (info .text )
461+ braille .handler .message (selectMessage )
462+
463+ ############################################################################
464+ # end of scripts section
465+ ############################################################################
0 commit comments