11# -*- coding: UTF-8 -*-
22# NumberProcessing
33# A global plugin for NVDA
4- # Copyright 2019 Alberto Buffolino, released under GPL
4+ # Copyright Alberto Buffolino, released under GPL
55# Add-on to read digit by digit any number of specified length
66# from an experimental idea of Derek Riemer
7- from gui import guiHelper , nvdaControls
8- from logHandler import log
9- from scriptHandler import getLastScriptRepeatCount
10- from versionInfo import version_year , version_major
117import addonHandler
128import config
139import globalPluginHandler
1410import globalVars
1511import gui
16- import os
1712import re
18- import speechDictHandler
13+ import speech
1914import ui
2015import wx
21- try :
22- from globalCommands import SCRCAT_SPEECH
23- except :
24- SCRCAT_SPEECH = None
2516
26- addonDir = os .path .join (os .path .dirname (__file__ ), ".." )
27- if isinstance (addonDir , bytes ):
28- addonDir = addonDir .decode ("mbcs" )
29- curAddon = addonHandler .Addon (addonDir )
30- addonSummary = curAddon .manifest ['summary' ]
17+ from enum import Enum
18+ from gui import guiHelper , nvdaControls , settingsDialogs
19+ from scriptHandler import script , getLastScriptRepeatCount
20+
3121
3222addonHandler .initTranslation ()
3323
4333)
4434confspec = {
4535 "autoEnable" : "boolean(default=false)" ,
46- "prevEnabled" : "boolean(default=false)" ,
4736 "userMinLen" : "integer(default=2)" ,
4837}
4938config .conf .spec ["numberProcessing" ] = confspec
50- backupProcessText = speechDictHandler .processText
51- visitedProfiles = set ()
52- globalEnabled = False
53- nvdaVersion = '.' .join ([str (version_year ), str (version_major )])
54- lastProfile = None
39+ profileStatus = {}
5540
56- def compileExps ( ):
57- exp1 = re . compile ( r'\d{%s,}' % userMinLen )
58- symbols = '' . join ( CURRENCY_SYMBOLS )
59- exp2 = re . compile ( r'([%s])?\s*([\d,.]+)' % symbols )
60- return ( exp1 , exp2 ,)
41+ class Status ( Enum ):
42+
43+ DISABLED = 0
44+ AUTO_ENABLED = 1
45+ MANUAL_ENABLED = 2
6146
6247# (re)load config
6348def loadConfig ():
64- global myConf , autoEnable , prevEnabled , userMinLen , exp1 , exp2
49+ global myConf , exp1 , exp2 , curProfile
6550 myConf = config .conf ["numberProcessing" ]
6651 autoEnable = myConf ["autoEnable" ]
67- prevEnabled = myConf ["prevEnabled" ]
6852 userMinLen = myConf ["userMinLen" ]
69- exp1 , exp2 = compileExps ()
53+ exp1 = re .compile (r'\d{%s,}' % userMinLen )
54+ symbols = '' .join (CURRENCY_SYMBOLS )
55+ exp2 = re .compile (r'([%s])?\s*([\d,.]+)' % symbols )
56+ curProfile = config .conf .profiles [- 1 ].name
57+ # adjust status for current profile
58+ if autoEnable :
59+ profileStatus [curProfile ] = Status .AUTO_ENABLED
60+ # adjust after disabling auto enable in settings
61+ elif profileStatus .get (curProfile , Status .DISABLED ) == Status .AUTO_ENABLED :
62+ profileStatus [curProfile ] = Status .DISABLED
7063
7164loadConfig ()
7265
66+ def filter_numberProcessing (speechSequence ):
67+ if not isEnabled ():
68+ return speechSequence
69+ newSpeechSequence = []
70+ for item in speechSequence :
71+ if not isinstance (item , str ):
72+ newSpeechSequence .append (item )
73+ continue
74+ # pre-process to avoid problems with decimal separator,
75+ # appending currency sign to the end of the amount
76+ item = exp2 .sub (r'\2\1' , item )
77+ # add whitespace around digits
78+ item = exp1 .sub (replaceFunc , item )
79+ newSpeechSequence .append (item )
80+ return newSpeechSequence
81+
82+ def isEnabled ():
83+ status = profileStatus .get (curProfile , Status .DISABLED )
84+ return bool (status .value )
85+
7386def replaceFunc (match ):
7487 # group(0) returns digits captured by match
7588 fixedText = ' ' .join (list (match .group (0 )))
7689 return fixedText
7790
78- def newProcessText (text ):
79- # pre-process to avoid problems with decimal separator,
80- # appending currency sign to the end of the amount
81- text = exp2 .sub (r'\2\1' , text )
82- # get spoken version
83- text = backupProcessText (text )
84- # add whitespace around digits
85- text = exp1 .sub (replaceFunc , text )
86- return text
87-
88- def enableProcessing ():
89- global prevEnabled , myConf , globalEnabled
90- speechDictHandler .processText = newProcessText
91- if autoEnable :
92- prevEnabled = myConf ["prevEnabled" ] = True
93- else :
94- globalEnabled = True
95-
96- def disableProcessing ():
97- global prevEnabled , myConf , globalEnabled
98- speechDictHandler .processText = backupProcessText
99- if autoEnable :
100- prevEnabled = myConf ["prevEnabled" ] = False
101- else :
102- globalEnabled = False
103-
104- # for settings presentation compatibility
105- if hasattr (gui .settingsDialogs , "SettingsPanel" ):
106- superDialogClass = gui .settingsDialogs .SettingsPanel
107- else :
108- superDialogClass = gui .SettingsDialog
10991
110- class NumberProcessingSettingsDialog ( superDialogClass ):
111- """Class to define settings dialog ."""
92+ class NumberProcessingSettings ( settingsDialogs . SettingsPanel ):
93+ """Class to define settings."""
11294
113- if hasattr (gui .settingsDialogs , "SettingsPanel" ):
114- # Translators: title of settings dialog
115- title = _ ("Number Processing" )
116- else :
117- # Translators: title of settings dialog
118- title = _ ("Number Processing Settings" )
95+ # Translators: title of settings dialog
96+ title = _ ("Number Processing" )
11997
120- # common to dialog and panel
12198 def makeSettings (self , settingsSizer ):
12299 loadConfig ()
123100 settingsSizerHelper = guiHelper .BoxSizerHelper (self , sizer = settingsSizer )
@@ -133,34 +110,13 @@ def makeSettings(self, settingsSizer):
133110 min = 2 ,
134111 initial = myConf ["userMinLen" ])
135112
136- # for dialog only
137- def postInit (self ):
138- self .autoEnableCheckBox .SetFocus ()
139-
140- # shared between onOk and onSave
141- def saveConfig (self ):
113+ def onSave (self ):
142114 # Update Configuration
143- prevAutoEnable = myConf ["autoEnable" ]
144115 myConf ["autoEnable" ] = self .autoEnableCheckBox .IsChecked ()
145116 myConf ["userMinLen" ] = self .userMinLenEdit .GetValue ()
146- # update global variables
117+ # reload new config
147118 loadConfig ()
148- if autoEnable :
149- #log.info("autoEnable after configuration")
150- enableProcessing ()
151- elif prevAutoEnable and prevEnabled :
152- #log.info("Fix status to avoid manual enabled at first script invocation")
153- global globalEnabled
154- globalEnabled = True
155119
156- # for dialog only
157- def onOk (self , evt ):
158- self .saveConfig ()
159- super (NumberProcessingSettingsDialog , self ).onOk (evt )
160-
161- # for panel only
162- def onSave (self ):
163- self .saveConfig ()
164120
165121class QuickSettingsDialog (wx .Dialog ):
166122
@@ -185,98 +141,58 @@ def __init__(self, parent, profileName):
185141 def onOk (self , evt ):
186142 # Update Configuration
187143 myConf ["userMinLen" ] = self .userMinLenEdit .GetValue ()
188- # update global variables
144+ # reload new config
189145 loadConfig ()
190146 self .Destroy ()
191147
192148class GlobalPlugin (globalPluginHandler .GlobalPlugin ):
193149
194- scriptCategory = addonSummary
150+ scriptCategory = addonHandler . getCodeAddon (). manifest [ "summary" ]
195151
196152 def __init__ (self , * args , ** kwargs ):
197153 super (GlobalPlugin , self ).__init__ (* args , ** kwargs )
198154 if globalVars .appArgs .secure :
199155 return
200156 self .createMenu ()
201157 loadConfig ()
202- if hasattr (config , "post_configProfileSwitch" ):
203- config .post_configProfileSwitch .register (self .handleConfigProfileSwitch )
204- if autoEnable :
205- #log.info("autoEnable at start")
206- enableProcessing ()
158+ config .post_configProfileSwitch .register (self .handleConfigProfileSwitch )
159+ speech .extensions .filter_speechSequence .register (filter_numberProcessing )
207160
208161 def createMenu (self ):
209- # Dialog or the panel.
210- if hasattr (gui .settingsDialogs , "SettingsPanel" ):
211- gui .settingsDialogs .NVDASettingsDialog .categoryClasses .append (NumberProcessingSettingsDialog )
212- else :
213- self .prefsMenu = gui .mainFrame .sysTrayIcon .menu .GetMenuItems ()[0 ].GetSubMenu ()
214- # Translators: menu item in preferences
215- self .NumberProcessingItem = self .prefsMenu .Append (wx .ID_ANY , _ ("Number Processing Settings..." ), "" )
216- gui .mainFrame .sysTrayIcon .Bind (wx .EVT_MENU , lambda e : gui .mainFrame ._popupSettingsDialog (NumberProcessingSettingsDialog ), self .NumberProcessingItem )
162+ gui .settingsDialogs .NVDASettingsDialog .categoryClasses .append (NumberProcessingSettings )
217163
218164 def terminate (self ):
219- if hasattr (gui .settingsDialogs , "SettingsPanel" ):
220- gui .settingsDialogs .NVDASettingsDialog .categoryClasses .remove (NumberProcessingSettingsDialog )
221- else :
222- try :
223- self .prefsMenu .RemoveItem (self .NumberProcessingItem )
224- except wx .PyDeadObjectError :
225- pass
226- loadConfig ()
227- disableProcessing ()
228-
165+ profileStatus .clear ()
166+ gui .settingsDialogs .NVDASettingsDialog .categoryClasses .remove (NumberProcessingSettings )
167+
168+ @script (
169+ # Translators: Message presented in input help mode.
170+ description = _ ("Pressed once, enables/disables digit processing; twice, launches quick settings" ),
171+ gesture = "kb:nvda+shift+l"
172+ )
229173 def script_toggleDigitManager (self , gesture , repeating = False ):
230174 if getLastScriptRepeatCount () and not repeating :
231175 self .launchQuickSettings ()
232176 self .script_toggleDigitManager (None , repeating = True )
233177 return
234178 loadConfig ()
235- if (autoEnable and not prevEnabled ) or (not autoEnable and not globalEnabled ):
236- #log.info("manual enabled")
237- enableProcessing ()
179+ if not isEnabled ():
180+ profileStatus [curProfile ] = Status .MANUAL_ENABLED
238181 message = _ ("Digit processing on" )
239182 else :
240- #log.info("manual disabled")
241- disableProcessing ()
183+ profileStatus [curProfile ] = Status .DISABLED
242184 message = _ ("Digit processing off" )
243185 if not repeating :
244186 ui .message (message )
245187
246- script_toggleDigitManager .__doc__ = _ ("Pressed once, enables/disables digit processing; twice, launches quick settings" )
247-
248188 def launchQuickSettings (self ):
249189 def run ():
250190 gui .mainFrame .prePopup ()
251- d = QuickSettingsDialog (None , config . conf . profiles [ - 1 ]. name )
191+ d = QuickSettingsDialog (None , curProfile )
252192 if d :
253193 d .Show ()
254194 gui .mainFrame .postPopup ()
255195 wx .CallAfter (run )
256196
257197 def handleConfigProfileSwitch (self ):
258- global curProfile
259198 loadConfig ()
260- profileName = config .conf .profiles [- 1 ].name
261- lastProfile = profileName
262- if autoEnable and profileName not in visitedProfiles :
263- visitedProfiles .add (profileName )
264- #log.info("autoEnable in %s for first time"%profileName)
265- enableProcessing ()
266- elif (autoEnable and prevEnabled ) or (not autoEnable and globalEnabled ):
267- #log.info("enable after changing profile")
268- enableProcessing ()
269- else :
270- #log.info("disable after changing profile")
271- disableProcessing ()
272-
273- def event_foreground (self , obj , nextHandler ):
274- if nvdaVersion < '2018.3' :
275- curProfile = config .conf .profiles [- 1 ].name
276- if lastProfile != curProfile :
277- self .handleConfigProfileSwitch ()
278- nextHandler ()
279-
280- __gestures = {
281- "kb:NVDA+shift+l" : "toggleDigitManager"
282- }
0 commit comments