Module GCalcScript
[hide private]
[frames] | no frames]

Source Code for Module GCalcScript

  1  ''' 
  2  Defines a special L{AEScript <AEScript.AEScript>} for the Calculator GCalc. 
  3  Reports buttons and their hotkeys as they are pressed. Reads the equation bar 
  4  intelligently. 
  5   
  6  @author: Joel Feiner 
  7  @author: Peter Parente 
  8  @organization: UNC Chapel Hill 
  9  @copyright: Copyright (c) 2007, Joel Feiner 
 10   
 11  @author: Frank Zenker 
 12  @author: Nicole Anacker 
 13  @author: Martina Weicht 
 14  @organization: IT Science Center Ruegen gGmbH, Germany 
 15  @copyright: Copyright (c) 2007, 2008 ITSC Ruegen 
 16   
 17  @license: I{The BSD License}. 
 18  All rights reserved. This program and the accompanying materials are made  
 19  available under the terms of the BSD license which is available at 
 20  U{http://www.opensource.org/licenses/bsd-license.php}. 
 21   
 22  @see: U{http://www.unc.edu/campus/policies/copyright.html} Section 5.D.2.A for 
 23  UNC copyright rules. 
 24  ''' 
 25  # import useful modules for Scripts 
 26  from AccessEngine import AEScript, AccessEngineAPI 
 27  from AccessEngine import AEConstants 
 28  from Tools.i18n import bind, _ 
 29   
 30  import logging 
 31  log = logging.getLogger('GCalcScript') 
 32   
 33  # metadata describing this Script 
 34  __uie__ = dict(kind='script', tier='gcalctool', all_tiers=False) 
 35   
36 -class GCalcScript(AEScript.EventScript):
37 ''' 38 A special L{AEScript <AEScript.AEScript>} for handling gcalctool. 39 It defines the hotkey I{Caps-Lock+I} that reads the equation. 40 41 @ivar old_string: Copy of what was in the equation box. 42 @type old_string: string 43 @ivar cleared: Set to C{True} if the equation box was cleared. 44 @type cleared: boolean 45 @ivar startup: Set to C{True} until the first caret event happens. 46 @type startup: boolean 47 @ivar tooltip: Set to C{True} if the tooltip is showing. 48 @type tooltip: boolean 49 '''
50 - def init(self):
51 ''' 52 Registers L{event tasks <AEScript.event_tasks>} to handle 53 L{caret <AEEvent.CaretChange>}, L{selector <AEEvent.SelectorChange>}, 54 L{state <AEEvent.StateChange>} and L{property <AEEvent.PropertyChange>} 55 events. Registers L{tasks <AEScript.registered_tasks>} that can be mapped to 56 L{AEInput.Gesture}s. 57 ''' 58 AccessEngineAPI.setScriptIdealOutput(self, 'audio') 59 60 self.old_string = "" 61 self.cleared = True 62 self.startup = True 63 self.tooltip = False 64 65 # register event tasks 66 self.registerEventTask('read caret', AEConstants.EVENT_TYPE_CARET_CHANGE, 67 tier=True) 68 self.registerEventTask('status bar', AEConstants.EVENT_TYPE_PROPERTY_CHANGE, 69 tier=True) 70 self.registerEventTask('read tooltip', AEConstants.EVENT_TYPE_STATE_CHANGE, 71 tier=True) 72 73 # register input events and commands 74 kbd = AccessEngineAPI.getInputDevice(None, 'keyboard') 75 AccessEngineAPI.addInputModifiers(self, kbd, kbd.AEK_CAPS_LOCK) 76 77 self.registerTask('read equation', self.readEquation) 78 self.registerCommand(kbd, 'read equation', _('read equation'), 79 False, [kbd.AEK_CAPS_LOCK, kbd.AEK_I]) 80 81 # do some work around normal announcements 82 self.registerTask('read button', self.readButtonName) 83 self.registerTask('stop reading', self.stopReading) 84 self.chainTask('read button', AEConstants.CHAIN_AROUND, 85 'read selector', 'BasicSpeechScript') 86 self.chainTask('stop reading', AEConstants.CHAIN_AROUND, 87 'read state', 'BasicSpeechScript')
88
89 - def getDescription(self):
90 ''' 91 Describe which L{AETier} this script applies to by default. 92 93 @return: Human readable translated description of this script. 94 @rtype: string 95 ''' 96 return _('Applies to gcalctool by default.')
97
98 - def _getFrameBoxText(self, por):
99 ''' 100 Return the entire equation. 101 102 @param por: Point of regard for the related accessible 103 @type por: L{AEPor} 104 @return: Entire equation 105 @rtype: string 106 ''' 107 # TextBox path: 0,1,0,0 108 frame = AccessEngineAPI.getRootAcc(por) 109 textBox = AccessEngineAPI.getAccFromPath(frame, 0, 1, 0, 0) 110 return AccessEngineAPI.getItemText(textBox)
111
112 - def _findHotkey(self, text):
113 ''' 114 Look for a hotkey and give it back. 115 116 @param text: Text which contained a hotkey 117 @type text: string 118 @return: Hotkey 119 @rtype: string 120 ''' 121 e = text.rfind(']') 122 if e > -1: 123 s = text.rfind('[') 124 if s > -1: 125 # get hotkey 126 return text[s+1:e] 127 128 e = text.rfind(')') 129 if e > -1: 130 s = text.rfind('(') 131 if s > -1: 132 return text[s+1:e]
133 ##### 134 ## Input Tasks 135 #####
136 - def stopReading(self, **kwargs):
137 ''' 138 The L{BasicSpeechScript.onStateChange 139 <BasicSpeechScript.BasicSpeechScript.onStateChange>}-method cuts off the 140 reading of the radiobutton details and reads the state twice. This chain 141 around to stop the L{BasicSpeechScript} and get the full detail infos: role, 142 accessible name, state and hotkey. 143 144 @param kwargs: Arbitrary keyword arguments to pass to the task 145 @type kwargs: dictionary 146 ''' 147 pass
148
149 - def readEquation(self, **kwargs):
150 ''' 151 Is invoked whenever user hits I{Caps-Lock+I}. It reads the equation 152 character by character. 153 154 @param kwargs: Arbitrary keyword arguments to pass to the task 155 @type kwargs: dictionary 156 @return: True to allow other tasks to process this event. 157 @rtype: boolean 158 159 @todo: NA: special characters like /*+-() are not translated 160 ''' 161 text = self._getFrameBoxText(kwargs['por']) 162 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 163 164 sf = AccessEngineAPI.getStyleVal(self, 'SpellFormat', **kwargs) 165 AccessEngineAPI.setStyleVal(self, 'SpellFormat', 166 AEConstants.FORMAT_SPELL, **kwargs) 167 AccessEngineAPI.sayInfo(self, cap='audio', role='output', 168 text=text, **kwargs) 169 AccessEngineAPI.setStyleVal(self, 'SpellFormat', sf, **kwargs) 170 return True
171 172 ##### 173 ## Tasks 174 #####
175 - def readButtonName(self, **kwargs):
176 ''' 177 Invoked when focus changes. We use this to announce the proper text of 178 buttons, radio buttons, and check buttons as the user arrows between them, 179 or uses Tab. 180 181 @param kwargs: Arbitrary keyword arguments to pass to the task 182 @type kwargs: dictionary 183 @return: True to allow other tasks to process this event. 184 @rtype: boolean 185 ''' 186 hotkey = None 187 if not self.tooltip: 188 if AccessEngineAPI.hasAccRole('push button', kwargs['por']): 189 # get the accessible name and the description of the buttons 190 name = AccessEngineAPI.getAccName(kwargs['por']) 191 desc = AccessEngineAPI.getAccDesc(kwargs['por']) 192 if desc: 193 hotkey = self._findHotkey(desc) 194 kwargs['text'] = name 195 self.doTask('read selector', 'BasicSpeechScript', chain=False, **kwargs) 196 197 # say the shortcut key too, after the normal announcement 198 if hotkey is not None: 199 kwargs['text'] = hotkey 200 AccessEngineAPI.sayHotkey(self, cap='audio', role='output', **kwargs) 201 return True
202 203 ###### 204 ### Event Tasks 205 ######
206 - def onCaretDeleted(self, text, **kwargs):
207 ''' 208 When a button is pressed: delete, move, insert events are fired in that 209 order. Uses the delete event to capture what the string was before the 210 new text is added by the insert. 211 212 @param text: Text before the changes 213 @type text: string 214 @param kwargs: Arbitrary keyword arguments to pass to the task 215 @type kwargs: dictionary 216 @return: True to allow other tasks to process this event. 217 @rtype: boolean 218 ''' 219 self.old_string = text 220 return True
221
222 - def onCaretInserted(self, text, **kwargs):
223 ''' 224 Reads text that is added to the equation, either by keyboard or by clicking 225 buttons. Announces the current text in the display area. 226 227 @param text: Text insterted 228 @type text: string 229 @param kwargs: Arbitrary keyword arguments to pass to the task 230 @type kwargs: dictionary 231 @return: C{True} to allow other tasks to process this event. 232 @rtype: boolean 233 ''' 234 if AccessEngineAPI.hasAccRole('edit bar', kwargs['por']): 235 236 # extract the change in the text startString = '' 237 endString = '' 238 startIndex = text.find(self.old_string) 239 240 # if we're just starting up, make sure to do the right thing with 241 # whatever's in the equation box 242 if self.startup == True: 243 self.startup = False 244 startString = '' 245 246 # question is whether to read everything here...there's no way to read 247 # the last thing entered. Hopefully, the user would start SUE 248 # before starting gcalctool to avoid this problem 249 endString = text 250 startIndex = 0 251 self.cleared = False 252 elif startIndex == -1: 253 # so we did not add any text at the beginning or end 254 if self.cleared: 255 # well, we were previously cleared, so now we're adding new text 256 # the 0 will be replaced by whatever was just added 257 startString = "" 258 endString = text 259 self.cleared = False 260 else: 261 if self.old_string == "0": 262 # just replaced a cleared calculator 263 startString = "" 264 endString = text 265 elif text != "0": 266 # not a clearing, but an equals, so we should read whole result 267 # ...but it could be a backspace, so let's check for that condition 268 # first (if new string is prefix of old string, we had a backspace) 269 if self.old_string.find(text) == 0: 270 startString = "" 271 endString = "" 272 else: 273 startString = "" 274 endString = text 275 else: 276 # we just got cleared, so mark it as such 277 self.cleared = True 278 else: 279 # this is the normal case: something got added, so let's say it 280 startString = text[0:startIndex] 281 endString = text[(startIndex + len(self.old_string)):] 282 283 if endString != "": 284 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 285 AccessEngineAPI.inhibitMayStop() 286 287 if self.cleared == True: 288 text=_('cleared') 289 elif startString == "1/(": 290 text=_('invert') 291 elif startString == "-(": 292 text=_('negate') 293 elif endString == "Sqrt(": 294 text=_('square root') 295 elif endString == '-': 296 text=_('minus') 297 elif endString == '*': 298 text=_('times') 299 elif endString == '/': 300 text=_('divides') 301 elif endString == '%': 302 text=_('percent') 303 elif endString == ' Mod ': 304 text=_('modulo') 305 elif endString == "Int(": 306 text=_('integer') 307 elif endString == "Abs(": 308 text=_('absolute value') 309 elif endString == "Frac(": 310 text=_('fraction') 311 elif endString != "": 312 text=endString 313 314 AccessEngineAPI.sayInfo(self, cap='audio', role='output', text=text, **kwargs) 315 return True
316
317 - def onStateChange(self, **kwargs):
318 ''' 319 Handles text being displayed as tooltip. 320 321 @param kwargs: Arbitrary keyword arguments to pass to the task 322 @type kwargs: dictionary 323 @return: C{True} to allow other tasks to process this event. 324 @rtype: boolean 325 ''' 326 if (kwargs['name'] == 'visible' and kwargs['value'] and 327 AccessEngineAPI.hasAccRole('tool tip', kwargs['por'])): 328 hotkey = None 329 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 330 tooltip = AccessEngineAPI.getAccName(kwargs['por']) 331 hotkey = self._findHotkey(tooltip) 332 if hotkey: 333 tooltip = tooltip[:tooltip.find(hotkey)-2] 334 AccessEngineAPI.sayInfo(self, cap='audio', role='output', 335 text=tooltip, **kwargs) 336 AccessEngineAPI.sayHotkey(self, cap='audio', role='output', 337 text = hotkey, **kwargs) 338 AccessEngineAPI.inhibitMayStop() 339 self.tooltip = True 340 else: 341 self.tooltip = False 342 return True
343
344 - def onPropertyChange(self, **kwargs):
345 ''' 346 Handles text being displayed in the statusbar. 347 348 @param kwargs: Arbitrary keyword arguments to pass to the task 349 @type kwargs: dictionary 350 @return: C{True} to allow other tasks to process this event. 351 @rtype: boolean 352 ''' 353 if kwargs['value']: 354 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 355 AccessEngineAPI.sayInfo(self, cap='audio', role='output', 356 text=kwargs['value'], **kwargs) 357 AccessEngineAPI.inhibitMayStop() 358 return True
359