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

Source Code for Module BasicSpeechScript

   1  ''' 
   2  Provides basic speech services and speech reports common to a screen reader. 
   3   
   4  @author: Peter Parente 
   5  @author: Pete Brunet 
   6  @author: Larry Weiss 
   7  @author: Brett Clippingdale 
   8  @organization: IBM Corporation 
   9  @copyright: Copyright (c) 2005, 2007 IBM Corporation 
  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 accompanies 
  20  this distribution, and is available at 
  21  U{http://www.opensource.org/licenses/bsd-license.php} 
  22  ''' 
  23  import AccessEngine 
  24  from AccessEngine import AEScript, AEEvent, AccessEngineAPI 
  25  from AccessEngine import AEConstants 
  26  from AccessEngine.AEPor import AEPor 
  27  import unicodedata, logging 
  28  from Tools.i18n import _ 
  29   
  30  # verbosity settings 
  31  SPEAK_NEVER = 0 
  32  SPEAK_ALWAYS = 1  
  33  SPEAK_DIFF = 2 
  34  SPEAK_LINE = 3 
  35  SPEAK_ALL = 4 
  36   
  37  __uie__ = dict(kind='script', all_tiers=True) 
  38  log = logging.getLogger('BasicSpeechScript') 
  39   
40 -class BasicSpeechScriptState(AEScript.ScriptState):
41 ''' 42 User configurable settings for basic speech service and reports. 43 44 B{WordEcho (bool):} Echo words when typing? 45 46 B{CharEcho (bool):} Echo characters when typing? 47 48 B{AutoLang (bool):} Switch languages automatically? 49 50 B{CharLimit (numeric):} Set the threshold for text insertion synthesizing. 51 If text exceeds this threshold, it will be summarized. 52 53 B{RoleVerbosity (bool):} Say role information always, only when it changes, 54 or never? 55 56 B{IndexVerbosity (enum):} Say the index and size of a collection always, 57 only when it changes, or never? 58 59 B{HeaderVerbosity (enum):} Say headers always, when they change in a table, 60 or never? 61 62 B{TextVerbosity (enum):} Say the line containing the caret or read all text 63 starting with the line containing the caret? 64 65 @ivar activewincnt: Used to track the number of active windows. 66 @type last_count: integer 67 '''
68 - def init(self):
69 ''' 70 Create L{AEState} settings for this L{AEScript <AEScript.AEScript>}. 71 ''' 72 self.newBool('WordEcho', False, _('Echo words?'), 73 _('When set, entire words are spoken when editing text.')) 74 self.newBool('CharEcho', True, _('Echo characters?'), 75 _('When set, individual characters are spoken when editing ' 76 'text.')) 77 self.newBool('AutoLang', True, _('Switch languages automatically?'), 78 _('When set, the language of the speech synthesizer tries to ' 79 'switch to the appropriate language and dialect ' 80 'automatically based on the current locale.')) 81 self.newNumeric('CharLimit', 5000, _('Summarize insertions longer than'), 82 0, 10000, 0, _('Set the threshold (in characters) for text' 83 ' insertions. Any text that exceeds this limit will be ' 84 ' summarized by its length.')) 85 self.newEnum('RoleVerbosity', SPEAK_DIFF, _('Say role'), 86 {_('Always') : SPEAK_ALWAYS, 87 _('When different') : SPEAK_DIFF, 88 _('Never') : SPEAK_NEVER}, 89 _('Always say widget roles, never say widget roles, or ' 90 'only say widget roles when they differ from the last ' 91 'report.')) 92 self.newEnum('IndexVerbosity', SPEAK_ALWAYS, _('Say index'), 93 {_('Always') : SPEAK_ALWAYS, 94 _('When different') : SPEAK_DIFF, 95 _('Never') : SPEAK_NEVER}, 96 _('Always say the index of an item and size of a collection, ' 97 'never say either, or only say index and size when they ' 98 'differ from the last report.')) 99 self.newEnum('HeaderVerbosity', SPEAK_DIFF, _('Say table headers'), 100 {_('Always') : SPEAK_ALWAYS, 101 _('When different') : SPEAK_DIFF, 102 _('Never') : SPEAK_NEVER}, 103 _('Always say table headers, never say table headers, ' 104 'or only say the headers when they differ from the last ' 105 'report.')) 106 self.newEnum('TextVerbosity', SPEAK_LINE, _('Say text area'), 107 {_('All') : SPEAK_ALL, 108 _('Line') : SPEAK_LINE, 109 _('Nothing') : SPEAK_NEVER}, 110 _('Read an entire multiline text area when it receives the ' 111 'focus, read only the line containing the caret, or read ' 112 'nothing.')) 113 114 # instance variables that are part of state but not a user settings 115 self.activewincnt = 0
116
117 - def getGroups(self):
118 ''' 119 Gets configurable settings for this L{AEScript <AEScript.AEScript>}. 120 121 @return: Group of all configurable settings 122 @rtype: L{AEState.Setting.Group} 123 ''' 124 g = self.newGroup() 125 s = g.newGroup(_('Input Echo')) 126 s.extend(['CharEcho', 'WordEcho']) 127 s = g.newGroup(_('Verbosity')) 128 s.extend(['RoleVerbosity', 'IndexVerbosity', 129 'HeaderVerbosity', 'TextVerbosity', 'CharLimit']) 130 s = g.newGroup(_('Language')) 131 s.extend(['AutoLang']) 132 return g
133
134 -class BasicSpeechScript(AEScript.EventScript):
135 ''' 136 Defines a default user interface that makes SUE act like a typical screen 137 reader. It defines special hotkeys. 138 - I{(Strg)} - stops the speech. 139 - I{(Alt+Shift+Page-Up)} - increase the speech rate 140 - I{(Alt+Shift+Page-Down)} - decrease the speech rate 141 - I{(Alt+Shift+?)} - reads the current position / "Where Am I" 142 - I{(Alt+Shift+T)} - reads the top 143 - I{(Alt+Shift+Down)} - read all 144 - I{(Alt+Shift+F)} - reads the text color and text attributes 145 - I{(Alt+Shift+D)} - reads the item description 146 147 @ivar start_por: Starting point of regard for the read operation 148 @type start_por: L{AEPor} 149 @ivar continue_por: Point of regard where reading should continue 150 @type continue_por: L{AEPor} 151 @ivar caret_changed: Tracks whether the last caret move event was a caused by 152 a change (insert/delete) or just navigation 153 @type caret_changed: boolean 154 155 @ivar last_caret: Stores the previous caret L{AEPor} 156 @type last_caret: L{AEPor} 157 @ivar last_role: Stores the previous role to avoid duplicate announcements 158 @type last_role: string 159 @ivar last_level: Stores the previous level to avoid duplicate announcements 160 @type last_level: integer 161 @ivar last_container: Stores the previously announced container name 162 @type last_container: string 163 @ivar last_sel_len: Length of the selected text when the last selection event 164 was received 165 @type last_sel_len: integer 166 @ivar last_row: Index of the last row activated 167 @type last_row: integer 168 @ivar last_col: Index of the last column activated 169 @type last_col: integer 170 @ivar last_count: Was count of items in a collection announced during last 171 announcement? 172 @type last_count: integer 173 ''' 174 # defines the class to use for state information in this Script 175 STATE = BasicSpeechScriptState 176
177 - def init(self):
178 ''' 179 Registers L{event tasks <AEScript.event_tasks>} to handle 180 L{focus <AEEvent.FocusChange>}, L{view <AEEvent.ViewChange>}, 181 L{caret <AEEvent.CaretChange>}, L{selector <AEEvent.SelectorChange>}, 182 L{state <AEEvent.StateChange>}, and L{property <AEEvent.PropertyChange>} 183 change events. Registers L{tasks <AEScript.registered_tasks>} that can be 184 mapped to L{AEInput.Gesture}s. 185 ''' 186 self.caret_changed = False 187 188 # set the default output device 189 AccessEngineAPI.setScriptIdealOutput(self, 'audio') 190 191 # register event handlers 192 self.registerEventTask('read focus', AEConstants.EVENT_TYPE_FOCUS_CHANGE) 193 self.registerEventTask('read view', AEConstants.EVENT_TYPE_VIEW_CHANGE, 194 all=True) 195 self.registerEventTask('read caret', AEConstants.EVENT_TYPE_CARET_CHANGE) 196 self.registerEventTask('read selector', 197 AEConstants.EVENT_TYPE_SELECTOR_CHANGE, 198 focus=True, tier=True) 199 self.registerEventTask('read state', AEConstants.EVENT_TYPE_STATE_CHANGE) 200 self.registerEventTask('read property', 201 AEConstants.EVENT_TYPE_PROPERTY_CHANGE) 202 203 # register input tasks 204 # these will be mapped to input gestures in other Scripts 205 self.registerTask('stop now', self.stopSpeech) 206 self.registerTask('increase speech rate', self.increaseRate) 207 self.registerTask('decrease speech rate', self.decreaseRate) 208 self.registerTask('read top', self.readTop) 209 self.registerTask('read description', self.readDescription) 210 self.registerTask('read new label', self.readNewLabel) 211 self.registerTask('read new role', self.readNewRole) 212 self.registerTask('read new level', self.readNewLevel) 213 self.registerTask('read new headers', self.readNewHeaders) 214 self.registerTask('read new container', self.readNewContainer) 215 self.registerTask('read item text', self.readItemText) 216 self.registerTask('read item index', self.readItemIndex) 217 self.registerTask('read item details', self.readItemDetails) 218 self.registerTask('read message', self.readMessage) 219 self.registerTask('read all', self.readAll) 220 self.registerTask('read review item', self.readReviewItem) 221 self.registerTask('read review skip', self.readReviewSkip) 222 self.registerTask('read pointer to por', self.readPointerToFocus) 223 224 # register cyclic input tasks 225 self.registerTask('read review char', self.readReviewChar) 226 self.registerTask('pronounce review char', self.pronounceReviewChar) 227 self.registerCyclicInputTasks('cycle review char', 228 'read review char', 229 'pronounce review char') 230 231 self.registerTask('read review word', self.readReviewWord) 232 self.registerTask('spell review word', self.spellReviewWord) 233 self.registerTask('pronounce review word', self.pronounceReviewWord) 234 self.registerCyclicInputTasks('cycle review word', 235 'read review word', 236 'spell review word', 237 'pronounce review word') 238 239 self.registerTask('read text color', self.readTextColor) 240 self.registerTask('read text attributes', self.readTextAttributes) 241 self.registerCyclicInputTasks('cycle text attributes', 242 'read text color', 243 'read text attributes') 244 245 self.registerTask('where am i now', self.whereAmINow) 246 self.registerTask('where am i ancestors', self.whereAmIAncestors) 247 self.registerCyclicInputTasks('cycle where am i', 248 'where am i now', 249 'where am i ancestors') 250 251 # link to review mode tasks 252 self.chainTask('read review item', AEConstants.CHAIN_AFTER, 253 'review previous item', 'ReviewScript') 254 self.chainTask('read review item', AEConstants.CHAIN_AFTER, 255 'review current item', 'ReviewScript') 256 self.chainTask('read review item', AEConstants.CHAIN_AFTER, 257 'review next item', 'ReviewScript') 258 259 self.chainTask('read review word', AEConstants.CHAIN_AFTER, 260 'review previous word', 'ReviewScript') 261 self.chainTask('cycle review word', AEConstants.CHAIN_AFTER, 262 'review current word', 'ReviewScript') 263 self.chainTask('read review word', AEConstants.CHAIN_AFTER, 264 'review next word', 'ReviewScript') 265 266 self.chainTask('read review char', AEConstants.CHAIN_AFTER, 267 'review previous char', 'ReviewScript') 268 self.chainTask('cycle review char', AEConstants.CHAIN_AFTER, 269 'review current char', 'ReviewScript') 270 self.chainTask('read review char', AEConstants.CHAIN_AFTER, 271 'review next char', 'ReviewScript') 272 273 self.chainTask('read pointer to por', AEConstants.CHAIN_AFTER, 274 'pointer to por', 'ReviewScript') 275 276 self.chainTask('read review skip', AEConstants.CHAIN_AFTER, 277 'review skip report', 'ReviewScript') 278 279 # get the Keyboard device and register modifiers and commands 280 kbd = AccessEngineAPI.getInputDevice(None, 'keyboard') 281 AccessEngineAPI.addInputModifiers(self, kbd, kbd.AEK_ALT_L, 282 kbd.AEK_SHIFT_L, kbd.AEK_ALT_R, 283 kbd.AEK_SHIFT_R, kbd.AEK_CAPS_LOCK) 284 285 # speech script 286 self.registerCommand(kbd, 'stop now', _('stop now'), 287 True, [kbd.AEK_CONTROL_R]) 288 self.registerCommand(kbd, 'stop now', _('stop now'), 289 True, [kbd.AEK_CONTROL_L]) 290 291 # TODO: Move this line to a better, more central place for every script to 292 # just call pair maybe AEConstants? 293 # register commands for either left Alt-Shift or right Alt-Shift 294 pairs = [[kbd.AEK_ALT_L, kbd.AEK_SHIFT_L], [kbd.AEK_ALT_R, kbd.AEK_SHIFT_R]] 295 296 # register input commands 297 for pair in pairs: 298 299 self.registerCommand(kbd, 'increase speech rate', 300 _('increase speech rate'), False, pair+[kbd.AEK_PAGE_UP]) 301 self.registerCommand(kbd, 'decrease speech rate', 302 _('decrease speech rate'), False, pair+[kbd.AEK_PAGE_DOWN]) 303 self.registerCommand(kbd, 'read top', 304 _('read top'), False, pair+[kbd.AEK_T]) 305 self.registerCommand(kbd, 'read description', 306 _('read description'), False, pair+[kbd.AEK_D]) 307 self.registerCommand(kbd, 'read all', 308 _('read all'), False, pair+[kbd.AEK_DOWN]) 309 self.registerCommand(kbd, 'cycle where am i', 310 _('cycle where am i'), False, pair+[kbd.AEK_QUESTION]) 311 self.registerCommand(kbd, 'cycle text attributes', 312 _('cycle text attributes'), False, pair+[kbd.AEK_F]) 313 314 # stateful variables; OK to access from other Task classes in this module 315 self.resetLasts()
316
317 - def resetLasts(self, container=''):
318 ''' 319 Resets variables tracking the last container, caret position, tree level, 320 selection length, row/col offsets, and row/col count on 321 L{view change <AEEvent.ViewChange>}. 322 323 @param container: Last container name announced 324 @type container: string 325 ''' 326 self.last_container = container 327 self.last_caret = AEPor(item_offset=0) 328 self.last_role = None 329 self.last_level = None 330 self.last_sel_len = 0 331 self.last_row = None 332 self.last_col = None 333 self.last_count = False
334
335 - def resetLastsOnFocus(self):
336 ''' 337 Resets variables tracking some of the stateful information on a 338 L{focus change <AEEvent.FocusChange>}. Does not reset the last role or last 339 container announced. 340 ''' 341 self.last_caret = AEPor(item_offset=0) 342 self.last_count = False 343 self.last_sel_len = 0 344 self.last_row = None 345 self.last_col = None 346 self.last_level = None
347
348 - def resetLastsForNew(self):
349 ''' 350 Resets variables tracking whether an item is new or not for 351 L{readNewRole}, L{readNewLevel}, L{readNewLabel}, etc. 352 ''' 353 self.last_role = None 354 self.last_count = False 355 self.last_sel_len = 0 356 self.last_row = None 357 self.last_col = None 358 self.last_level = None
359
360 - def getName(self):
361 ''' 362 Provides the localized name of this L{AEScript <AEScript.AEScript>}. 363 364 @return: Human readable name of this script. 365 @rtype: string 366 ''' 367 return _('Basic speech')
368
369 - def getDescription(self):
370 ''' 371 Describe what this special L{AEScript} do. 372 373 @return: Human readable translated description of this script. 374 @rtype: string 375 ''' 376 return _('Manages basic screen reader speech output. Requires a loaded ' 377 'speech device.')
378
379 - def switchLanguages(self, por, layer):
380 ''' 381 Attempts to switch the active speech engine to the POSIX message language 382 in use in this L{AETier}. 383 384 @param por: Point of regard for the related accessible 385 @type por: L{AEPor} 386 @param layer: Layer on which the event occurred 387 @type layer: integer 388 ''' 389 if not self.getScriptSettingVal('AutoLang'): 390 # do not switch language automatically if the user has disabled the 391 # feature 392 return 393 # get the local string from the application 394 l = AccessEngineAPI.getAccAppLocale(por) 395 if l is None: 396 # no locale information provided 397 return 398 # convert the string to IANA format for the speech devices 399 tag = AccessEngineAPI.convertPOSIXToIANA(l) 400 # ask the active device to find a language close to the one specified 401 tag = AccessEngineAPI.getClosestLang(self, layer, tag, 402 cap='audio', role='output') 403 if tag is not None: 404 # set the tag as the active language for all voices on the device 405 AccessEngineAPI.setStyleVal(self, 'Language', tag, layer=layer)
406 407 ############## 408 ## InputTask 409 ##############
410 - def stopSpeech(self, **kwargs):
411 ''' 412 Task to stop speech immediately, ignoring the value of the Stopping setting. 413 414 @param kwargs: Arbitrary keyword arguments to pass to the task 415 @type kwargs: dictionary 416 ''' 417 AccessEngineAPI.stopAll(self) 418 try: 419 self.unregisterTimerTask('read all continued') 420 except KeyError: 421 pass
422
423 - def increaseRate(self, **kwargs):
424 ''' 425 Increase the speech rate. The maximum rate is announced when reached. 426 @see: L{decreaseRate} 427 428 @param kwargs: Arbitrary keyword arguments to pass to the task 429 @type kwargs: dictionary 430 ''' 431 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 432 rate = AccessEngineAPI.getStyleSetting(self, 'Rate', **kwargs) 433 # make up a step size 434 step = (abs(rate.max)+abs(rate.min))/30 435 if rate.value+step < rate.max: 436 rate.value += step 437 AccessEngineAPI.sayInfo(self, text=rate.value, template=_('rate %d'), 438 cap='audio', role='output', **kwargs) 439 else: 440 AccessEngineAPI.sayInfo(self, text=_('maximum rate'), 441 cap='audio', role='output', **kwargs)
442
443 - def decreaseRate(self, **kwargs):
444 ''' 445 Decrease the speech rate. The minimum rate is announced when reached. 446 @see: L{increaseRate} 447 448 @param kwargs: Arbitrary keyword arguments to pass to the task 449 @type kwargs: dictionary 450 ''' 451 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 452 rate = AccessEngineAPI.getStyleSetting(self, 'Rate', **kwargs) 453 # make up a step size 454 step = (abs(rate.max)+abs(rate.min))/30 455 if rate.value-step > rate.min: 456 rate.value -= step 457 AccessEngineAPI.sayInfo(self, text=rate.value, template=_('rate %d'), 458 cap='audio', role='output', **kwargs) 459 else: 460 AccessEngineAPI.sayInfo(self, text=_('minimum rate'), 461 cap='audio', role='output', **kwargs)
462
463 - def readReviewItem(self, **kwargs):
464 ''' 465 Reads the details of the item at the pointer or speaks an informational 466 message about why the item cannot be read. 467 468 @param kwargs: Arbitrary keyword arguments to pass to the task 469 @type kwargs: dictionary 470 ''' 471 result = self.getTempData('review') 472 if not self.getTempData('has skipped'): 473 # only stop if we haven't already skipped another item, otherwise we 474 # might still be speaking 475 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 476 if result == AEConstants.REVIEW_OK: 477 kwargs['por'] = self.getVirtualPOR() 478 kwargs['text'] = AccessEngineAPI.getItemText(kwargs['por']) 479 self.doTask('read item details', **kwargs) 480 elif result == AEConstants.REVIEW_NO_NEXT_ITEM: 481 kwargs['por'] = self.getVirtualPOR() 482 kwargs['text'] =_('last item') 483 AccessEngineAPI.sayInfo(self, cap='audio', role='output', **kwargs) 484 elif result == AEConstants.REVIEW_NO_PREV_ITEM: 485 kwargs['por'] = self.getVirtualPOR() 486 kwargs['text'] = _('first item') 487 AccessEngineAPI.sayInfo(self, cap='audio', role='output', **kwargs)
488
489 - def readReviewSkip(self, **kwargs):
490 ''' 491 Reads a summary of an item skipped during review. 492 493 @param kwargs: Arbitrary keyword arguments to pass to the task 494 @type kwargs: dictionary 495 ''' 496 if not self.getTempData('has skipped'): 497 # be sure to stop speech if we haven't skipped other items yet 498 AccessEngineAPI.stopNow(self, cap='audio', role='output', 499 reset=False, **kwargs) 500 self.doTask('read new role', **kwargs)
501
502 - def readReviewWord(self, text=None, **kwargs):
503 ''' 504 Reads the word at the pointer or speaks an informational message about why 505 the word cannot be read. 506 507 @param text: Text to say, or C{None} to use the pointer L{AEPor} by default 508 @type text: string 509 @param kwargs: Arbitrary keyword arguments to pass to the task 510 @type kwargs: dictionary 511 ''' 512 result = self.getTempData('review') 513 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 514 # allow None case to pass as normal so this task can be reused to read 515 # words in cases other than reviewing 516 kwargs['por'] = self.getVirtualPOR() 517 if (result is None or result == AEConstants.REVIEW_OK or 518 result == AEConstants.REVIEW_WRAP): 519 AccessEngineAPI.sayWord(self, text=text, 520 cap='audio', role='output', **kwargs) 521 elif result == AEConstants.REVIEW_NO_NEXT_ITEM: 522 AccessEngineAPI.sayInfo(self, text=_('last item'), 523 cap='audio', role='output', **kwargs) 524 elif result == AEConstants.REVIEW_NO_PREV_ITEM: 525 AccessEngineAPI.sayInfo(self, text=_('first item'), 526 cap='audio', role='output', **kwargs) 527 elif result == AEConstants.REVIEW_NO_NEXT_WORD: 528 AccessEngineAPI.sayInfo(self, text=_('last word'), 529 cap='audio', role='output', **kwargs) 530 elif result == AEConstants.REVIEW_NO_PREV_WORD: 531 AccessEngineAPI.sayInfo(self, text=_('first word'), 532 cap='audio', role='output', **kwargs)
533
534 - def readReviewChar(self, **kwargs):
535 ''' 536 Reads the character at the pointer or speaks an informational message about 537 why the item cannot be read. 538 539 @param kwargs: Arbitrary keyword arguments to pass to the task 540 @type kwargs: dictionary 541 ''' 542 result = self.getTempData('review') 543 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 544 kwargs['por'] = self.getVirtualPOR() 545 if result == AEConstants.REVIEW_OK or result == AEConstants.REVIEW_WRAP: 546 AccessEngineAPI.sayChar(self, cap='audio', role='output', **kwargs) 547 elif result == AEConstants.REVIEW_NO_NEXT_ITEM: 548 AccessEngineAPI.sayInfo(self, text=_('last item'), 549 cap='audio', role='output', **kwargs) 550 elif result == AEConstants.REVIEW_NO_PREV_ITEM: 551 AccessEngineAPI.sayInfo(self, text=_('first item'), 552 cap='audio', role='output', **kwargs) 553 elif result == AEConstants.REVIEW_NO_NEXT_CHAR: 554 AccessEngineAPI.sayInfo(self, text=_('last char'), 555 cap='audio', role='output', **kwargs) 556 elif result == AEConstants.REVIEW_NO_PREV_CHAR: 557 AccessEngineAPI.sayInfo(self, text=_('first char'), 558 cap='audio', role='output', **kwargs)
559
560 - def spellReviewWord(self, text=None, **kwargs):
561 ''' 562 Spells the word at the current L{AEPor}. Says translated names for known 563 symbols and punctuation. Says Unicode values for unknown characters. 564 565 @param text: Text to say, or C{None} to use the pointer L{AEPor} by default 566 @type text: string 567 @param kwargs: Arbitrary keyword arguments to pass to the task 568 @type kwargs: dictionary 569 ''' 570 sem = AEConstants.SEM_WORD 571 # get the spelling format setting 572 sf = AccessEngineAPI.getStyleSetting(self, 'SpellFormat', sem=sem, **kwargs) 573 # store the old value and set the new one 574 old = sf.value 575 sf.value = AEConstants.FORMAT_SPELL 576 # say the current word with the style changed 577 kwargs['por'] = self.getVirtualPOR() 578 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 579 AccessEngineAPI.sayWord(self, text=text, 580 cap='audio', role='output', **kwargs) 581 # restore the old value 582 sf.value = old
583
584 - def pronounceReviewWord(self, text=None, **kwargs):
585 ''' 586 Spells the word at the current L{AEPor} using the phonetic alphabet and 587 names for known symbols and punctuation. Says Unicode values and 588 classifications for unknown characters. 589 590 @param text: Text to say, or C{None} to use the pointer L{AEPor} by default 591 @type text: string 592 @param kwargs: Arbitrary keyword arguments to pass to the task 593 @type kwargs: dictionary 594 ''' 595 sem = AEConstants.SEM_WORD 596 # get the spelling format setting 597 sf = AccessEngineAPI.getStyleSetting(self, 'SpellFormat', sem=sem, **kwargs) 598 # store the old value and set the new one 599 old = sf.value 600 sf.value = AEConstants.FORMAT_PHONETIC 601 # say the current word with the style changed 602 kwargs['por'] = self.getVirtualPOR() 603 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 604 AccessEngineAPI.sayWord(self, text=text, 605 cap='audio', role='output', **kwargs) 606 # restore the old value 607 sf.value = old
608
609 - def pronounceReviewChar(self, **kwargs):
610 ''' 611 Says the current character using the phonetic alphabet and names for known 612 symbols and punctuation. Says Unicode values and classifications for unknown 613 characters. 614 615 @param kwargs: Arbitrary keyword arguments to pass to the task 616 @type kwargs: dictionary 617 ''' 618 sem = AEConstants.SEM_CHAR 619 # get the spelling format setting 620 sf = AccessEngineAPI.getStyleSetting(self, 'SpellFormat', sem=sem, **kwargs) 621 # store the old value and set the new one 622 old = sf.value 623 sf.value = AEConstants.FORMAT_PHONETIC 624 # say the current word with the style changed 625 kwargs['por'] = self.getVirtualPOR() 626 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 627 AccessEngineAPI.sayChar(self, cap='audio', role='output', **kwargs) 628 # restore the old value 629 sf.value = old
630
631 - def readPointerToFocus(self, por, **kwargs):
632 ''' 633 Reports the new location of the pointer L{AEPor} when it snaps back to the 634 application focus, selection, and caret position. 635 636 @param por: Point of regard for the related accessible 637 @type por: L{AEPor} 638 @param kwargs: Arbitrary keyword arguments to pass to the task 639 @type kwargs: dictionary 640 ''' 641 self.doTask('read focus', gained = True, por = self.getVirtualPOR(), 642 **kwargs) 643 self.doTask('read item details', por = por, **kwargs)
644
645 - def whereAmIAncestors(self, **kwargs):
646 ''' 647 Reports all control and container names in child-to-parent order from the 648 current L{AEPor} to the root accessible. 649 650 @param kwargs: Arbitrary keyword arguments to pass to the task 651 @type kwargs: dictionary 652 ''' 653 # reset all variables tracking whether information is "new" 654 self.resetLastsForNew() 655 for curr in AccessEngineAPI.iterAncestorAccs(kwargs['por']): 656 # announce role and text 657 kwargs['por'] = curr 658 AccessEngineAPI.sayRole(self, cap='audio', role='output', **kwargs) 659 AccessEngineAPI.sayItem(self, cap='audio', role='output', **kwargs)
660
661 - def whereAmINow(self, **kwargs):
662 ''' 663 Reports the details of the current L{AEPor}. 664 665 @param kwargs: Arbitrary keyword arguments to pass to the task 666 @type kwargs: dictionary 667 ''' 668 # reset all variables tracking whether information is "new" 669 self.resetLastsForNew() 670 # make sure verbosity is all the way up 671 r, i, h = (self.state.RoleVerbosity, self.state.IndexVerbosity, 672 self.state.HeaderVerbosity) 673 self.state.RoleVerbosity = SPEAK_ALWAYS 674 self.state.IndexVerbosity = SPEAK_ALWAYS 675 self.state.HeaderVerbosity = SPEAK_ALWAYS 676 # and then announce all details 677 self.doTask('read item details', **kwargs) 678 self.state.RoleVerbosity = r 679 self.state.IndexVerbosity = i 680 self.state.HeaderVerbosity = h
681
682 - def readItemText(self, text, **kwargs):
683 ''' 684 Reads the text of the current item. 685 686 @param text: Text to say 687 @type text: string 688 @param kwargs: Arbitrary keyword arguments to pass to the task 689 @type kwargs: dictionary 690 ''' 691 # announce the item text only if it is different than the label 692 #text = AccessEngineAPI.getItemText(kwargs['por']) 693 label = AccessEngineAPI.getAccLabel(kwargs['por']) 694 if (label != text and text != ''): 695 AccessEngineAPI.sayItem(self, text=text, 696 cap='audio', role='output', **kwargs)
697
698 - def readItemIndex(self, por=None, **kwargs):
699 ''' 700 Speaks the row and column indices of the given L{AEPor}. Also speaks the 701 total number of items if it has not been announced previously. 702 703 @param por: Point of regard to an object possibly having an index of 704 interest 705 @type por: L{AEPor} 706 @param kwargs: Arbitrary keyword arguments to pass to the task 707 @type kwargs: dictionary 708 ''' 709 iv = self.getScriptSettingVal('IndexVerbosity') 710 if iv == SPEAK_NEVER: 711 # say nothing as the user demands 712 return 713 714 # get the number of rows and cols 715 exts = AccessEngineAPI.getAccTableExtents(por) 716 if exts is not None: 717 # get the row index 718 row = AccessEngineAPI.getAccRow(por) 719 if row is None: 720 # no index to report, happens on a container 721 return 722 # add one to the row offset to make it 1-based for the user 723 row += 1 724 rows, cols = exts 725 else: 726 # get attributes for position and size 727 row = AccessEngineAPI.getAccPosInSet(por) 728 rows = AccessEngineAPI.getAccSetSize(por) 729 if row is None or rows is None: 730 # no index or size, not a table or list, abort 731 return 732 cols = 0 733 734 if cols <= 1: 735 # 1D lists 736 if rows is not None and (iv == SPEAK_ALWAYS or not self.last_count): 737 text = (row, rows) 738 template = _('item %d of %d') 739 else: 740 text = row+1 # NIC: TEST 741 template = _('item %d') # NIC: TEST 742 else: 743 # 2D tables 744 col = AccessEngineAPI.getAccColumn(por) 745 if iv == SPEAK_ALWAYS or not self.last_count: 746 try: 747 text = (row, col+1, rows, cols) 748 except TypeError: 749 # not in a cell 750 return 751 template = _('item %d, %d, of %d, %d') 752 else: 753 try: 754 text = (row, col+1) # NIC: TEST 755 756 except TypeError: 757 # not in a cell 758 return 759 template = _('item %d, %d') # NIC: TEST 760 761 AccessEngineAPI.sayIndex(self, text=text, template=template, por=por, 762 cap='audio', role='output', **kwargs) 763 self.last_count = True
764
765 - def readNewHeaders(self, por=None, **kwargs):
766 ''' 767 Reads the row/column headers if they differ from the last ones announced. 768 Speaks the row and column headers of the given L{AEPor}. Only makes an 769 announcement if the last row/col encountered by this task or this 770 readNewHeaders task is different from the current. 771 772 @param por: Point of regard to a table cell possibly having a header 773 @type por: L{AEPor} 774 @param kwargs: Arbitrary keyword arguments to pass to the task 775 @type kwargs: dictionary 776 ''' 777 hv = self.getScriptSettingVal('HeaderVerbosity') 778 if hv == SPEAK_NEVER: 779 # never speaking headers according to user settings 780 return 781 # get the current row 782 row = AccessEngineAPI.getAccRow(por) 783 if row is not None and (hv == SPEAK_ALWAYS or row != self.last_row): 784 # speak if setting is always speak, or if the row changed if diff speak 785 header = AccessEngineAPI.getAccRowHeader(por) 786 if header: 787 AccessEngineAPI.saySection(self, text=header, template=_('row %s'), 788 por=por, cap='audio', role='output', **kwargs) 789 self.last_row = row 790 # get the current column 791 col = AccessEngineAPI.getAccColumn(por) 792 if col is not None and (hv == SPEAK_ALWAYS or col != self.last_col): 793 # speak if setting is always speak, or if the col changed if diff speak 794 header = AccessEngineAPI.getAccColumnHeader(por) 795 if header: 796 AccessEngineAPI.saySection(self, text=header, template=_('column %s'), 797 por=por, cap='audio', role='output', **kwargs) 798 self.last_col = col
799
800 - def readNewRole(self, por=None, role=None, say_role=True, **kwargs):
801 ''' 802 Speaks the role of the given L{AEPor} if it is different from the last role 803 read. Depends on the RoleVerbosity setting. Does an explicit check for the 804 identity of the task invoking this one to account for double role 805 announcements from both focus and selector events. 806 807 @param por: Point of regard to the accessible whose role should be said 808 @type por: L{AEPor} 809 @param role: Translated role name to speak. Prefers using this if available 810 over fetching the string from the L{AEPor} 811 @type role: string 812 @param say_role: Recommendation based on data other than the L{last_role} 813 to take into account when the role verbosity is set to always so that 814 we don't double announce roles. 815 @type say_role: boolean 816 @param kwargs: Arbitrary keyword arguments to pass to the task 817 @type kwargs: dictionary 818 ''' 819 # get the verbosity setting value 820 val = self.getScriptSettingVal('RoleVerbosity') 821 if val == SPEAK_NEVER: 822 # never say roles 823 return 824 if role is None: 825 # fetch the role name 826 role = AccessEngineAPI.getAccRoleName(por) 827 828 if (val == SPEAK_ALWAYS and say_role) or self.last_role != role: 829 # take the say_role flag into account to make sure we don't repeat role 830 # announcements immediately on events 831 kwargs['text'] = role 832 AccessEngineAPI.sayRole(self, por=por, 833 cap='audio', role='output', **kwargs) 834 self.last_role = role
835
836 - def readNewLevel(self, por=None, **kwargs):
837 ''' 838 Reads the tree level of a widget if it's different from the last level read. 839 Speaks the level of the given L{AEPor} only if it is different than the last 840 level spoken by this method. 841 842 @param por: Point of regard to the accessible whose role should be said 843 @type por: L{AEPor} 844 @param kwargs: Arbitrary keyword arguments to pass to the task 845 @type kwargs: dictionary 846 ''' 847 level = AccessEngineAPI.getAccLevel(por) 848 if level is not None and level != self.last_level: 849 # root is 1 850 AccessEngineAPI.sayLevel(self, text=level+1, template=_('level %d'), 851 por=por, cap='audio', role='output', **kwargs) 852 self.last_level = level
853
854 - def readNewLabel(self, por=None, **kwargs):
855 ''' 856 Read the label on a widget if it is different from the item text of the 857 widget and the last container label read. 858 Speaks the text of a new label at the given L{AEPor} only if it is different 859 than the item text and the last container announced by L{readNewContainer}. 860 861 @param por: Point of regard to an accessible that should be announced if it 862 is a new label 863 @type por: L{AEPor} 864 @param kwargs: Arbitrary keyword arguments to pass to the task 865 @type kwargs: dictionary 866 ''' 867 label = AccessEngineAPI.getAccLabel(por) 868 if (label and label != self.last_container): 869 AccessEngineAPI.sayLabel(self, text=label, por=por, 870 cap='audio', role='output', **kwargs)
871
872 - def readNewContainer(self, por=None, **kwargs):
873 ''' 874 Speaks the first interesting ancestor of the given L{AEPor} if it is 875 different from the last interesting ancestor announced. 876 877 @param por: Point of regard to an accessible that should be announced if it 878 is a new menu. 879 @type por: L{AEPor} 880 @param kwargs: Arbitrary keyword arguments to pass to the task 881 @type kwargs: dictionary 882 ''' 883 # get the root accessible 884 root = AccessEngineAPI.getRootAcc(por) 885 for anc in AccessEngineAPI.iterAncestorAccs(por, allow_trivial=True): 886 # don't count the application itself 887 if AccessEngineAPI.hasAccRole('application', anc): 888 break 889 to_check = [anc] 890 # try the previous peer too if it's a label 891 prev = AccessEngineAPI.getPrevPeerAcc(anc) 892 if (prev and AccessEngineAPI.hasAccRole('label', prev) and 893 not AccessEngineAPI.getAccRelations('label for', prev)): 894 to_check.append(prev) 895 last = self.last_container 896 for acc in to_check: 897 name_text = AccessEngineAPI.getAccName(acc) 898 label_text = AccessEngineAPI.getAccLabel(acc) 899 if (((name_text and last.startswith(name_text)) or 900 (label_text and last.startswith(label_text)) or 901 AccessEngineAPI.hasAccRole('menu bar', acc))): 902 return 903 if name_text and name_text.strip(): 904 # try to announce the name text first 905 AccessEngineAPI.saySection(self, text=name_text, por=por, 906 cap='audio', role='output', **kwargs) 907 self.last_container = name_text 908 return 909 if label_text and label_text.strip(): 910 # then try the label 911 AccessEngineAPI.saySection(self, text=label_text, por=por, 912 cap='audio', role='output', **kwargs) 913 self.last_container = label_text 914 return
915
916 - def readMessage(self, text, sem=AEConstants.SEM_INFO, **kwargs):
917 ''' 918 Makes an arbitrary announcement using the given semantic for styling. 919 920 @param text: Text to announce 921 @type text: string 922 @param sem: Semantic of the announcement. Defaults to info. 923 @type sem: integer 924 @param kwargs: Arbitrary keyword arguments to pass to the task 925 @type kwargs: dictionary 926 ''' 927 AccessEngineAPI.say(self, cap='audio', role='output', text=text, 928 layer=kwargs['layer'], por=kwargs['por'], sem=sem)
929
930 - def readAll(self, interval=None, **kwargs):
931 ''' 932 Read the remaining contents of the active view starting at the current 933 point of regard. 934 935 @param interval: Interval in seconds on which the timer fires. 936 @type interval: integer 937 @param kwargs: Arbitrary keyword arguments to pass to the task 938 @type kwargs: dictionary 939 ''' 940 self.start_por = None 941 self.continue_por = None 942 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 943 self.registerTimerTask('read all continued', 1000, self._continueReadAll) 944 # read the current item again first 945 self.doTask('review current item', 'ReviewScript', **kwargs) 946 self.doTask('read all continued', **kwargs)
947
948 - def _continueReadAll(self, **kwargs):
949 ''' 950 Continue queuing up contents in the view on a timed interval. 951 952 @param kwargs: Arbitrary keyword arguments to pass to the task 953 @type kwargs: dictionary 954 955 @note: if index markers are supported, update the L{start_por} accordingly 956 so a stop leaves the user near the last reviewed location, or maybe this is 957 an option? 958 ''' 959 # initialize the start point 960 por_text = self.start_por 961 self.start_por = self.start_por or self.getVirtualPOR() 962 # always read as if focused 963 self.layer = AEConstants.LAYER_FOCUS 964 if self.getVirtualPOR() == self.start_por: 965 temp_por = self.continue_por or self.getVirtualPOR() 966 self.setVirtualPOR(temp_por) 967 # continue where we left off 968 for i in xrange(5): 969 # always indicate items have been skipped so we don't stop the speech 970 # announcing them 971 self.setTempData('has skipped', True) 972 # only invoke the review task, not all other chained to it 973 self.doTask('review next item', 'ReviewScript', 974 chain=False, **kwargs) 975 # now invoke the speech task for reviewing items 976 self.doTask('read review item', chain=False, stop=False, **kwargs) 977 if self.getTempData('review') == AEConstants.REVIEW_NO_NEXT_ITEM: 978 # if the review script indicates no next item, stop reading 979 self.unregisterTimerTask(kwargs['task_name']) 980 break 981 else: 982 # task por changed since last continuation, abort 983 self.unregisterTimerTask(kwargs['task_name']) 984 # store the last review position 985 self.continue_por = self.getVirtualPOR() 986 # restore the starting por 987 self.setVirtualPOR(self.start_por)
988
989 - def readTop(self, **kwargs):
990 ''' 991 Read top of the active view, typically the title of the foreground window. 992 993 @param kwargs: Arbitrary keyword arguments to pass to the task 994 @type kwargs: dictionary 995 ''' 996 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 997 AccessEngineAPI.sayWindow(self, cap='audio', role='output', **kwargs)
998
999 - def _getAttrs(self, **kwargs):
1000 ''' 1001 Reusable method for getting default and current text attributes and 1002 merging them into one dictionary. 1003 1004 @param kwargs: Arbitrary keyword arguments to pass to the task 1005 @type kwargs: dictionary 1006 @return: Dictionary of name/value attribute pairs 1007 @rtype: dictionary 1008 ''' 1009 attrs = AccessEngineAPI.getAccAllTextAttrs(kwargs['por']) 1010 dattrs = AccessEngineAPI.getAccDefTextAttrs(kwargs['por']) 1011 1012 if not attrs and not dattrs: 1013 # no atttribute information 1014 AccessEngineAPI.sayInfo(self, text=_('text attributes unavailable'), 1015 cap='audio', role='output', **kwargs) 1016 return None 1017 elif not attrs: 1018 # only default attribute information 1019 attrs = dattrs 1020 elif not dattrs: 1021 # only current attribute information 1022 pass 1023 else: 1024 # overwrite default with current 1025 dattrs.update(attrs) 1026 attrs = dattrs 1027 return attrs
1028
1029 - def readTextColor(self, **kwargs):
1030 ''' 1031 Read the text foreground and background color names followed by their values. 1032 1033 @param kwargs: Arbitrary keyword arguments to pass to the task 1034 @type kwargs: dictionary 1035 ''' 1036 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 1037 ## get merged attributes 1038 attrs = self._getAttrs(**kwargs) 1039 if attrs is None: 1040 return 1041 1042 # get foreground value 1043 fgval = attrs.get('fg-color') 1044 fgname = AccessEngineAPI.getColorString(fgval) or _('default foreground') 1045 1046 # get background value 1047 bgval = attrs.get('bg-color') 1048 bgname = AccessEngineAPI.getColorString(bgval) or _('default background') 1049 1050 # i18n: foreground on background color (e.g. "black on white") 1051 template = _('%s on %s.') 1052 names = template % (fgname, bgname) 1053 if bgval is None and fgval is None: 1054 AccessEngineAPI.sayColor(self, text=names, 1055 cap='audio', role='output', **kargs) 1056 elif bgval is not None and fgval is not None: 1057 AccessEngineAPI.sayColor(self, text=(fgval, bgval), 1058 template=names + ' ' + template, cap='audio', 1059 role='output', **kwargs) 1060 elif fgval is not None: 1061 AccessEngineAPI.sayColor(self, text=fgval, template=names + ' %s', 1062 cap='audio', role='output', **kwargs) 1063 elif bgval is not None: 1064 AccessEngineAPI.sayColor(self, text=bgval, template=names + ' %s', 1065 cap='audio', role='output', **kwargs)
1066
1067 - def readTextAttributes(self, **kwargs):
1068 ''' 1069 Read all remaining text attribute name/value pairs sans color information. 1070 1071 @param kwargs: Arbitrary keyword arguments to pass to the task 1072 @type kwargs: dictionary 1073 ''' 1074 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 1075 attrs = self._getAttrs(**kwargs) 1076 for name, value in attrs.items(): 1077 if not name.endswith('color'): 1078 AccessEngineAPI.sayTextAttrs(self, text=(name, value), template='%s: %s.', 1079 cap='audio', role='output', **kwargs)
1080
1081 - def readDescription(self, **kwargs):
1082 ''' 1083 Read the description of the accessible at the current point of regard. 1084 1085 @param kwargs: Arbitrary keyword arguments to pass to the task 1086 @type kwargs: dictionary 1087 ''' 1088 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 1089 AccessEngineAPI.sayDesc(self, cap='audio', role='output', **kwargs)
1090
1091 - def readItemDetails(self, text=None, say_role=True, **kwargs):
1092 ''' 1093 Reads all details of the current item. 1094 1095 @param text: Item text to use in the announcement 1096 @type text: string 1097 @param say_role: Recommendation about whether the role should be announced 1098 or not purely based on the type of event received 1099 @type say_role: boolean 1100 @param kwargs: Arbitrary keyword arguments to pass to the task 1101 @type kwargs: dictionary 1102 ''' 1103 if text is None: 1104 text = AccessEngineAPI.getItemText(kwargs['por']) 1105 # announce the item role only if the caller asks for it, otherwise we 1106 # could be making a repeat announcement 1107 self.doTask('read new role', say_role=say_role, **kwargs) 1108 # read the item text 1109 self.doTask('read item text', text=text, **kwargs) 1110 # try to announce the headers on the row and column 1111 self.doTask('read new headers', **kwargs) 1112 # see if there are any states worth reporting 1113 AccessEngineAPI.sayState(self, cap='audio', role='output', **kwargs) 1114 # try to announce the level 1115 self.doTask('read new level', **kwargs) 1116 # try to announce the item index 1117 self.doTask('read item index', **kwargs) 1118 # try to announce the total number of items 1119 #self.doTask('read item count', **kwargs) # TODO: where registered?? 1120 # get the hotkey for this item 1121 AccessEngineAPI.sayHotkey(self, cap='audio', role='output', **kwargs) 1122 if not text: 1123 # only announce value if it isn't encoded in the item text 1124 AccessEngineAPI.sayValue(self, cap='audio', role='output', **kwargs) 1125 # i18n hint: a numeric range by a step (e.g. 0.0 to 100.0 by 2.0) 1126 AccessEngineAPI.sayValueExtents(self, template=_('%.1f to %.1f by %.1f'), 1127 cap='audio', role='output', **kwargs)
1128 1129 ############### 1130 ## View Change 1131 ###############
1132 - def onViewFirstGained(self, **kwargs):
1133 ''' 1134 Announces the title of the first view to be activated, but without stopping 1135 previous speech which is likely to be the SUE welcome message. Inhibits the 1136 next stop to avoid never announcing the title because of another immediate 1137 event following this one (i.e. focus). 1138 1139 @param kwargs: Arbitrary keyword arguments to pass to the task 1140 @type kwargs: dictionary 1141 @return: C{True} to allow other tasks to process this event. 1142 @rtype: boolean 1143 ''' 1144 # say the title of the new view 1145 if kwargs['title'] != '': 1146 AccessEngineAPI.sayWindow(self, cap='audio', role='output', **kwargs) 1147 # reset stateful vars 1148 self.resetLasts(kwargs['title']) 1149 # increment active window count 1150 self.state.activewincnt += 1 1151 return True
1152
1153 - def onViewGained(self, **kwargs):
1154 ''' 1155 Stops output then calls L{onViewFirstGained} to reuse its code for 1156 announcing the new view. 1157 1158 @param kwargs: Arbitrary keyword arguments to pass to the task 1159 @type kwargs: dictionary 1160 @return: C{True} to allow other tasks to process this event. 1161 @rtype: boolean 1162 ''' 1163 # stop output 1164 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 1165 # switch to a language matching the application locale 1166 self.switchLanguages(kwargs['por'], kwargs['layer']) 1167 rv = self.onViewFirstGained(**kwargs) 1168 # prevent the next event (focus?) from stopping this announcement 1169 AccessEngineAPI.inhibitMayStop() 1170 # see if we want to handle this new view in a special way 1171 role = AccessEngineAPI.getAccRole(kwargs['por']) 1172 if role == 'alert': 1173 try: 1174 self.registerTask('read alert', self.readAlert) 1175 self.bindToEvent('read alert', AEConstants.EVENT_TYPE_FOCUS_CHANGE) 1176 except ValueError: 1177 pass 1178 elif role == 'dialog': 1179 try: 1180 self.registerTask('read dialog', self.readDialog) 1181 self.bindToEvent('read dialog', AEConstants.EVENT_TYPE_FOCUS_CHANGE) 1182 except ValueError: 1183 pass 1184 return rv
1185
1186 - def onViewLost(self, **kwargs):
1187 ''' 1188 Decrement the active window count. If no active window when make an 1189 announcement. 1190 1191 @param kwargs: Arbitrary keyword arguments to pass to the task 1192 @type kwargs: dictionary 1193 @return: C{True} to allow other tasks to process this event. 1194 @rtype: boolean 1195 1196 @todo: disabled announcement for the time being 1197 ''' 1198 # decrement active window count 1199 self.state.activewincnt -= 1 1200 #start timer if there are no active windows 1201 if self.state.activewincnt == 0: 1202 self.registerTimerTask('in view timer', 1500, self.inViewTimer) 1203 return True
1204
1205 - def inViewTimer(self, **kwargs):
1206 ''' 1207 Executes some time after a View lost event to check for an active window. 1208 Makes an announcement if no window is active. 1209 1210 @param kwargs: Arbitrary keyword arguments to pass to the task 1211 @type kwargs: dictionary 1212 ''' 1213 # make sure there are still no active windows 1214 if self.state.activewincnt == 0: 1215 AccessEngineAPI.stopNow(self, cap='audio', role='output', **kwargs) 1216 AccessEngineAPI.sayInfo(self, text=_('no active window'), 1217 cap='audio', role='output', **kwargs) 1218 self.unregisterTimerTask(kwargs['task_name'])
1219 1220 ################# 1221 ## Focus Change 1222 #################
1223 - def onFocusGained(self, **kwargs):
1224 ''' 1225 Annouces the label and role of the newly focused widget. The role is output 1226 only if it is different from the last one announced. 1227 1228 @param kwargs: Arbitrary keyword arguments to pass to the task 1229 @type kwargs: dictionary 1230 @return: C{True} to allow other tasks to process this event. 1231 @rtype: boolean 1232 ''' 1233 self.setVirtualPOR(kwargs['por']) 1234 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1235 # announce containers when they change 1236 self.doTask('read new container', **kwargs) 1237 # announce the label when present if it is not the same as the name or the 1238 # container 1239 self.doTask('read new label', **kwargs) 1240 # only announce role when it has changed 1241 self.doTask('read new role', **kwargs) 1242 # prevent the next selector or caret from stopping this announcement 1243 AccessEngineAPI.inhibitMayStop() 1244 # reset stateful variables 1245 self.resetLastsOnFocus() 1246 1247 # test: reset the variable, to read the Combo-box after a text edit in evolution 1248 self.caret_changed = False 1249 1250 # return true, so that more task can execute for the current event 1251 return True
1252 1253 ################### 1254 ## SelectorChange 1255 ###################
1256 - def onSelectorActive(self, **kwargs):
1257 ''' 1258 Announces the text of the active item. 1259 1260 @param kwargs: Arbitrary keyword arguments to pass to the task 1261 @type kwargs: dictionary 1262 @return: C{True} to allow other tasks to process this event. 1263 @rtype: boolean 1264 ''' 1265 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1266 say_role = not isinstance(AccessEngine.AEEventManager.last_event, 1267 AEEvent.FocusChange) 1268 self.doTask('read item details', say_role=say_role, **kwargs)
1269
1270 - def onSelectorText(self, text, **kwargs):
1271 ''' 1272 Announces the localized word selected or unselected followed by the total 1273 number of characters in the selection. 1274 1275 Single line text boxes fire selection events followed by a caret event. 1276 Multiline text boxes fire events in the opposite order. The 1277 L{Output.inhibitMayStop <AccessEngineAPI.Output.inhibitMayStop>} allows 1278 the selection event to complete for single line text boxes before the caret 1279 event stops it with a call to 1280 L{Output.mayStop <AccessEngineAPI.Output.mayStop>}. 1281 1282 @param kwargs: Arbitrary data given by the observer. 1283 @type kwargs: dictionary 1284 ''' 1285 if AccessEngineAPI.getAccCaret(kwargs['por']) != self.tier.getPointer(): 1286 # if the caret is out of sync with our pointer, we haven't received a 1287 # movement event yet corresponding to this selection; inhibit the next 1288 # may stop as the next event will be the movement 1289 AccessEngineAPI.inhibitMayStop() 1290 n = len(text) 1291 if n >= self.last_sel_len: 1292 AccessEngineAPI.sayTextAttrs(self, text=n, template=_('selected %d'), 1293 cap='audio', role='output', **kwargs) 1294 elif self.last_sel_len != 0: 1295 # don't say unselection when the last selection length was zero also 1296 AccessEngineAPI.sayTextAttrs(self, text=(self.last_sel_len-n, n),\ 1297 template=_('unselected %d, %d remaining'), 1298 cap='audio', role='output', **kwargs) 1299 self.last_sel_len = n
1300 1301 ################# 1302 ## Caret Change 1303 #################
1304 - def onCaretChange(self, **kwargs):
1305 ''' 1306 Stores the point of regard to the caret event location. Tries to stop 1307 current output. 1308 1309 @param kwargs: Arbitrary keyword arguments to pass to the task 1310 @type kwargs: dictionary 1311 @return: C{True} to allow other tasks to process this event. 1312 @rtype: boolean 1313 ''' 1314 # let the base class call the appropriate method for inset, move, delete 1315 self.setVirtualPOR(kwargs['por']) 1316 rv = AEScript.EventScript.onCaretChange(self, **kwargs) 1317 # store for next comparison 1318 self.last_caret = kwargs['por'] 1319 return rv
1320
1321 - def onCaretInserted(self, **kwargs):
1322 ''' 1323 Announces the inserted text. 1324 1325 @param kwargs: Arbitrary keyword arguments to pass to the task 1326 @type kwargs: dictionary 1327 @return: C{True} to allow other tasks to process this event. 1328 @rtype: boolean 1329 ''' 1330 text = kwargs['text'] 1331 self.caret_changed = True 1332 if len(text) == 1: 1333 w = (self.state.WordEcho and unicodedata.category(text)[0] in ('C', 'Z')) 1334 c = self.state.CharEcho 1335 if w and c: 1336 # word and character echo 1337 #s = self.getStyle(layer=AEConstants.LAYER_TIER, sem=AEConstants.SEM_WORD) 1338 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1339 AccessEngineAPI.sayChar(self, cap='audio', role='output', **kwargs) 1340 # Don't repeat 'blank' announcement 1341 word_text= AccessEngineAPI.getWordText(kwargs['por']) 1342 if word_text.strip(): 1343 AccessEngineAPI.say(self, text=word_text, layer=AEConstants.LAYER_TIER, 1344 cap='audio', role='output', por=kwargs['por'], 1345 sem=AEConstants.SEM_WORD) 1346 elif w: 1347 # word echo 1348 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1349 AccessEngineAPI.sayWord(self, cap='audio', role='output', **kwargs) 1350 elif c: 1351 # character echo 1352 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1353 AccessEngineAPI.sayChar(self, cap='audio', role='output', **kwargs) 1354 else: 1355 # chunk of text, if length is longer than CharLimit summarize 1356 # it (speak text length), else say the whole thing 1357 if len(text) > self.state.CharLimit: 1358 template=_('%d characters inserted') 1359 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1360 kwargs['text'] = len(text) 1361 AccessEngineAPI.sayInfo(self, template=template, 1362 cap='audio', role='output', **kwargs) 1363 else: 1364 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1365 AccessEngineAPI.sayItem(self, cap='audio', role='output', **kwargs) 1366 return True
1367
1368 - def onCaretMoved(self, **kwargs):
1369 ''' 1370 Announces the text passed in the caret movement. 1371 1372 @param kwargs: Arbitrary keyword arguments to pass to the task 1373 @type kwargs: dictionary 1374 @return: C{True} to allow other tasks to process this event. 1375 @rtype: boolean 1376 1377 @note: MW: With caret movement LSR used to speak the string between the old 1378 and the new caret position. This behaviour is not consistent with braille 1379 output and confuses a lot. Therefore, this was changed so we'll always speak 1380 the character after the caret position which is what other screen reader do, 1381 too. More functionality is provided by the review keys. 1382 ''' 1383 # don't echo again if a change just happened 1384 if self.caret_changed: 1385 self.caret_changed = False 1386 return 1387 1388 if not kwargs['por'].isSameAcc(self.last_caret): 1389 # say the current line or read the entire text depending on 1390 # the user setting for text verbosity 1391 tv = self.getScriptSettingVal('TextVerbosity') 1392 if tv == SPEAK_NEVER: 1393 return 1394 elif tv == SPEAK_LINE: 1395 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1396 AccessEngineAPI.sayItem(self, cap='audio', role='output', **kwargs) 1397 if (AccessEngineAPI.hasAccState('multi line', kwargs['por']) and 1398 AccessEngineAPI.getAccTextSelectionCount(kwargs['por']) > 0): 1399 # announce the selection if this is a multiline box as we often do not 1400 # receive an event; klude that probably breaks for non-gtk apps 1401 1402 kwargs['text'] = len(AccessEngineAPI.getAccTextSelection(kwargs['por'])[0]) 1403 AccessEngineAPI.sayTextAttrs(self, template=_('selected %s'), 1404 cap='audio', role='output', **kwargs) 1405 else: 1406 # read everything starting from the caret position 1407 self.doTask('read all', **kwargs) 1408 elif not kwargs['por'].isSameItem(self.last_caret): 1409 # say the new line when item_offset changes 1410 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1411 AccessEngineAPI.sayItem(self, cap='audio', role='output', **kwargs) 1412 else: 1413 # respond to caret movement on same line 1414 1415 text = kwargs['text'] 1416 #if por.isCharBefore(self.last_caret): 1417 ## moved left, say text between positions 1418 #diff = text[kwargs['por'].char_offset : self.last_caret.char_offset] 1419 #AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1420 #kwargs['text'] = diff 1421 #if len(diff) > 1: 1422 #AccessEngineAPI.sayItem(self, cap='audio', role='output', **kwargs) 1423 #else: 1424 #AccessEngineAPI.sayChar(self, cap='audio', role='output', **kwargs) 1425 #elif kwargs['por'].isCharAfter(self.last_caret): 1426 ## moved right, say text between positions 1427 #diff = text[self.last_caret.char_offset : kwargs['por'].char_offset] 1428 #AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1429 #kwargs['text'] = diff 1430 #if len(diff) > 1: 1431 #AccessEngineAPI.sayItem(self, cap='audio', role='output', **kwargs) 1432 #else: 1433 #AccessEngineAPI.sayChar(self, cap='audio', role='output', **kwargs) 1434 1435 # TODO: bei 'Backspace' wird das Zeichen nach dem Cursor angesagt, 1436 # anstatt das geloeschte Zeichen 1437 # no matter if moved left or right, always say character to the right of caret 1438 if (kwargs['por'].char_offset+1) <= len(text): 1439 char = text[kwargs['por'].char_offset] 1440 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1441 kwargs['text'] = char 1442 AccessEngineAPI.sayChar(self, cap='audio', role='output', **kwargs) 1443 self.may_stop = 0 1444 return True
1445
1446 - def onCaretDeleted(self, **kwargs):
1447 ''' 1448 Announces the deleted text. Prepends delete and backspace text to indicate 1449 which way the caret moved. 1450 1451 @param kwargs: Arbitrary keyword arguments to pass to the task 1452 @type kwargs: dictionary 1453 ''' 1454 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1455 if (self.last_caret.item_offset + 1456 self.last_caret.char_offset) > kwargs['text_offset']: 1457 # say backspace if we're backing up 1458 AccessEngineAPI.sayItem(self, template=_('backspace %s'), 1459 cap='audio', role='output', **kwargs) 1460 self.changed = True 1461 else: 1462 # say delete if we're deleting the current 1463 AccessEngineAPI.sayItem(self, template=_('delete %s'), 1464 cap='audio', role='output', **kwargs) 1465 self.changed = False
1466
1467 - def updateOnCaretChange(self, por, text, text_offset, added, **kwargs):
1468 ''' 1469 Updates the last caret cache with the current L{AEPor}, but only if the 1470 temp value of 'no update' has not been set to C{True}. 1471 1472 @param por: Point of regard where the caret event occurred 1473 @type por: L{AEPor} 1474 @param text: The text inserted, deleted or the line of the caret 1475 @type text: string 1476 @param text_offset: The offset of the inserted/deleted text or the line 1477 offset when movement only 1478 @type text_offset: integer 1479 @param added: C{True} when text added, c{False} when text deleted, 1480 and C{None} when event is for caret movement only 1481 @type added: boolean 1482 @param kwargs: Arbitrary keyword arguments to pass to the task 1483 @type kwargs: dictionary 1484 ''' 1485 if self.getTempData('no update'): 1486 return 1487 # store for next comparison 1488 self.last_caret = por 1489 self.caret_changed = False
1490 1491 ################# 1492 ## State Change 1493 #################
1494 - def onStateChange(self, **kwargs):
1495 ''' 1496 Announces state changes according to the ones of interest defined by 1497 L{View.getStateText <AccessEngineAPI.View.getStateText>}. 1498 1499 @param kwargs: Arbitrary keyword arguments to pass to the task 1500 @type kwargs: dictionary 1501 ''' 1502 text = AccessEngineAPI.getStateText(kwargs['por'], kwargs['name'], 1503 kwargs['value']) 1504 if text: 1505 # try a may stop 1506 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1507 AccessEngineAPI.sayState(self, text=text, 1508 cap='audio', role='output', **kwargs)
1509 1510 #################### 1511 ## Property Change 1512 ####################
1513 - def onPropertyChange(self, **kwargs):
1514 ''' 1515 Announces value changes. 1516 1517 @param kwargs: Arbitrary keyword arguments to pass to the task 1518 @type kwargs: dictionary 1519 @return: C{True} to allow other tasks to process this event. 1520 @rtype: boolean 1521 ''' 1522 if kwargs['name'] == 'value': 1523 if not AccessEngineAPI.getItemText(kwargs['por']): 1524 # only announce value if we're not going to get a text change event 1525 # immediately after saying the same thing 1526 AccessEngineAPI.mayStop(self, cap='audio', role='output', **kwargs) 1527 AccessEngineAPI.sayItem(self, text=value, 1528 cap='audio', role='output', **kwargs) 1529 return True
1530
1531 - def readAlert(self, **kwargs):
1532 ''' 1533 Task registered temporarily when an alert dialog is shown. Collects all 1534 labels of interest in the dialog and says them in order. 1535 1536 @param kwargs: Arbitrary keyword arguments to pass to the task 1537 @type kwargs: dictionary 1538 @return: C{True} to allow other tasks to process this event. 1539 @rtype: boolean 1540 ''' 1541 rootPOR = AccessEngineAPI.getViewRootAcc() 1542 # start iteration at the root 1543 for por in AccessEngineAPI.iterNextAccs(rootPOR): 1544 # announce all labels 1545 if AccessEngineAPI.hasAccRole('label', por): 1546 kwargs['por'] = por 1547 AccessEngineAPI.sayItem(self, cap='audio', role='output', **kwargs) 1548 # unregister this task 1549 self.unbindFromEvent(kwargs['task_name'], 1550 AEConstants.EVENT_TYPE_FOCUS_CHANGE) 1551 self.unregisterTask(kwargs['task_name']) 1552 return True
1553
1554 - def readDialog(self, **kwargs):
1555 ''' 1556 Task registered temporarily when a dialog (not alert) is shown. Announces 1557 first two labels in the dialog in the order they were traversed. Traversal 1558 of some containers is bypassed. 1559 1560 @param kwargs: Arbitrary keyword arguments to pass to the task 1561 @type kwargs: dictionary 1562 @return: C{True} to allow other tasks to process this event. 1563 @rtype: boolean 1564 ''' 1565 count = 0 1566 por = AccessEngineAPI.getViewRootAcc() 1567 # start iteration at the root 1568 while por is not None: 1569 role = AccessEngineAPI.getAccRole(por) 1570 if AccessEngineAPI.hasAccRole('label', por) and count < 2: 1571 text = AccessEngineAPI.getItemText(por) 1572 if text.strip(): 1573 kwargs['por'] = por 1574 AccessEngineAPI.sayItem(self, cap='audio', role='output', **kwargs) 1575 count += 1 1576 elif count >= 2: 1577 break 1578 # don't traverse some containers 1579 if AccessEngineAPI.hasOneAccRole(por, 'page tab','table','push button',\ 1580 'combo box'): 1581 por = AccessEngineAPI.getNextPeerAcc(por) 1582 # get next por walker traverses 1583 else: 1584 por = AccessEngineAPI.getNextAcc(por) 1585 # unbind and unregister this task 1586 self.unbindFromEvent(kwargs['task_name'], 1587 AEConstants.EVENT_TYPE_FOCUS_CHANGE) 1588 self.unregisterTask(kwargs['task_name']) 1589 return True
1590