1# Orca 2# 3# Copyright 2004-2009 Sun Microsystems Inc. 4# Copyright 2010 Joanmarie Diggs 5# 6# This library is free software; you can redistribute it and/or 7# modify it under the terms of the GNU Lesser General Public 8# License as published by the Free Software Foundation; either 9# version 2.1 of the License, or (at your option) any later version. 10# 11# This library is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14# Lesser General Public License for more details. 15# 16# You should have received a copy of the GNU Lesser General Public 17# License along with this library; if not, write to the 18# Free Software Foundation, Inc., Franklin Street, Fifth Floor, 19# Boston MA 02110-1301 USA. 20 21"""The default Script for presenting information to the user using 22both speech and Braille. This is based primarily on the de-facto 23standard implementation of the AT-SPI, which is the GAIL support 24for GTK.""" 25 26__id__ = "$Id$" 27__version__ = "$Revision$" 28__date__ = "$Date$" 29__copyright__ = "Copyright (c) 2004-2009 Sun Microsystems Inc." \ 30 "Copyright (c) 2010 Joanmarie Diggs" 31__license__ = "LGPL" 32 33import pyatspi 34import re 35import time 36 37import gi 38gi.require_version('Atspi', '2.0') 39from gi.repository import Atspi 40import orca.braille as braille 41import orca.cmdnames as cmdnames 42import orca.debug as debug 43import orca.eventsynthesizer as eventsynthesizer 44import orca.find as find 45import orca.flat_review as flat_review 46import orca.guilabels as guilabels 47import orca.input_event as input_event 48import orca.keybindings as keybindings 49import orca.messages as messages 50import orca.orca as orca 51import orca.orca_gui_commandlist as commandlist 52import orca.orca_state as orca_state 53import orca.phonnames as phonnames 54import orca.script as script 55import orca.settings as settings 56import orca.settings_manager as settings_manager 57import orca.sound as sound 58import orca.speech as speech 59import orca.speechserver as speechserver 60import orca.mouse_review as mouse_review 61import orca.notification_messages as notification_messages 62 63_settingsManager = settings_manager.getManager() 64 65######################################################################## 66# # 67# The Default script class. # 68# # 69######################################################################## 70 71class Script(script.Script): 72 73 EMBEDDED_OBJECT_CHARACTER = '\ufffc' 74 NO_BREAK_SPACE_CHARACTER = '\u00a0' 75 76 # generatorCache 77 # 78 DISPLAYED_LABEL = 'displayedLabel' 79 DISPLAYED_TEXT = 'displayedText' 80 KEY_BINDING = 'keyBinding' 81 NESTING_LEVEL = 'nestingLevel' 82 NODE_LEVEL = 'nodeLevel' 83 REAL_ACTIVE_DESCENDANT = 'realActiveDescendant' 84 85 def __init__(self, app): 86 """Creates a new script for the given application. 87 88 Arguments: 89 - app: the application to create a script for. 90 """ 91 script.Script.__init__(self, app) 92 93 self.flatReviewContext = None 94 self.windowActivateTime = None 95 self.targetCursorCell = None 96 97 self.justEnteredFlatReviewMode = False 98 99 self.digits = '0123456789' 100 self.whitespace = ' \t\n\r\v\f' 101 102 # A dictionary of non-standardly-named text attributes and their 103 # Atk equivalents. 104 # 105 self.attributeNamesDict = {} 106 107 # Keep track of the last time we issued a mouse routing command 108 # so that we can guess if a change resulted from our moving the 109 # pointer. 110 # 111 self.lastMouseRoutingTime = None 112 113 # The last location of the mouse, which we might want if routing 114 # the pointer elsewhere. 115 # 116 self.oldMouseCoordinates = [0, 0] 117 118 # Used to copy/append the current flat review contents to the 119 # clipboard. 120 # 121 self.currentReviewContents = "" 122 123 self._lastWordCheckedForSpelling = "" 124 125 self._inSayAll = False 126 self._sayAllIsInterrupted = False 127 self._sayAllContexts = [] 128 self.grab_ids = [] 129 130 if app: 131 app.setCacheMask(pyatspi.cache.DEFAULT ^ pyatspi.cache.NAME ^ pyatspi.cache.DESCRIPTION) 132 133 def setupInputEventHandlers(self): 134 """Defines InputEventHandler fields for this script that can be 135 called by the key and braille bindings.""" 136 137 self.inputEventHandlers["routePointerToItemHandler"] = \ 138 input_event.InputEventHandler( 139 Script.routePointerToItem, 140 cmdnames.ROUTE_POINTER_TO_ITEM) 141 142 self.inputEventHandlers["leftClickReviewItemHandler"] = \ 143 input_event.InputEventHandler( 144 Script.leftClickReviewItem, 145 cmdnames.LEFT_CLICK_REVIEW_ITEM) 146 147 self.inputEventHandlers["rightClickReviewItemHandler"] = \ 148 input_event.InputEventHandler( 149 Script.rightClickReviewItem, 150 cmdnames.RIGHT_CLICK_REVIEW_ITEM) 151 152 self.inputEventHandlers["sayAllHandler"] = \ 153 input_event.InputEventHandler( 154 Script.sayAll, 155 cmdnames.SAY_ALL) 156 157 self.inputEventHandlers["flatReviewSayAllHandler"] = \ 158 input_event.InputEventHandler( 159 Script.flatReviewSayAll, 160 cmdnames.SAY_ALL_FLAT_REVIEW) 161 162 self.inputEventHandlers["whereAmIBasicHandler"] = \ 163 input_event.InputEventHandler( 164 Script.whereAmIBasic, 165 cmdnames.WHERE_AM_I_BASIC) 166 167 self.inputEventHandlers["whereAmIDetailedHandler"] = \ 168 input_event.InputEventHandler( 169 Script.whereAmIDetailed, 170 cmdnames.WHERE_AM_I_DETAILED) 171 172 self.inputEventHandlers["whereAmILinkHandler"] = \ 173 input_event.InputEventHandler( 174 Script.whereAmILink, 175 cmdnames.WHERE_AM_I_LINK) 176 177 self.inputEventHandlers["whereAmISelectionHandler"] = \ 178 input_event.InputEventHandler( 179 Script.whereAmISelection, 180 cmdnames.WHERE_AM_I_SELECTION) 181 182 self.inputEventHandlers["getTitleHandler"] = \ 183 input_event.InputEventHandler( 184 Script.presentTitle, 185 cmdnames.PRESENT_TITLE) 186 187 self.inputEventHandlers["getStatusBarHandler"] = \ 188 input_event.InputEventHandler( 189 Script.presentStatusBar, 190 cmdnames.PRESENT_STATUS_BAR) 191 192 self.inputEventHandlers["findHandler"] = \ 193 input_event.InputEventHandler( 194 orca.showFindGUI, 195 cmdnames.SHOW_FIND_GUI) 196 197 self.inputEventHandlers["findNextHandler"] = \ 198 input_event.InputEventHandler( 199 Script.findNext, 200 cmdnames.FIND_NEXT) 201 202 self.inputEventHandlers["findPreviousHandler"] = \ 203 input_event.InputEventHandler( 204 Script.findPrevious, 205 cmdnames.FIND_PREVIOUS) 206 207 self.inputEventHandlers["toggleFlatReviewModeHandler"] = \ 208 input_event.InputEventHandler( 209 Script.toggleFlatReviewMode, 210 cmdnames.TOGGLE_FLAT_REVIEW) 211 212 self.inputEventHandlers["reviewPreviousLineHandler"] = \ 213 input_event.InputEventHandler( 214 Script.reviewPreviousLine, 215 cmdnames.REVIEW_PREVIOUS_LINE) 216 217 self.inputEventHandlers["reviewHomeHandler"] = \ 218 input_event.InputEventHandler( 219 Script.reviewHome, 220 cmdnames.REVIEW_HOME) 221 222 self.inputEventHandlers["reviewCurrentLineHandler"] = \ 223 input_event.InputEventHandler( 224 Script.reviewCurrentLine, 225 cmdnames.REVIEW_CURRENT_LINE) 226 227 self.inputEventHandlers["reviewSpellCurrentLineHandler"] = \ 228 input_event.InputEventHandler( 229 Script.reviewSpellCurrentLine, 230 cmdnames.REVIEW_SPELL_CURRENT_LINE) 231 232 self.inputEventHandlers["reviewPhoneticCurrentLineHandler"] = \ 233 input_event.InputEventHandler( 234 Script.reviewPhoneticCurrentLine, 235 cmdnames.REVIEW_PHONETIC_CURRENT_LINE) 236 237 self.inputEventHandlers["reviewNextLineHandler"] = \ 238 input_event.InputEventHandler( 239 Script.reviewNextLine, 240 cmdnames.REVIEW_NEXT_LINE) 241 242 self.inputEventHandlers["reviewEndHandler"] = \ 243 input_event.InputEventHandler( 244 Script.reviewEnd, 245 cmdnames.REVIEW_END) 246 247 self.inputEventHandlers["reviewPreviousItemHandler"] = \ 248 input_event.InputEventHandler( 249 Script.reviewPreviousItem, 250 cmdnames.REVIEW_PREVIOUS_ITEM) 251 252 self.inputEventHandlers["reviewAboveHandler"] = \ 253 input_event.InputEventHandler( 254 Script.reviewAbove, 255 cmdnames.REVIEW_ABOVE) 256 257 self.inputEventHandlers["reviewCurrentItemHandler"] = \ 258 input_event.InputEventHandler( 259 Script.reviewCurrentItem, 260 cmdnames.REVIEW_CURRENT_ITEM) 261 262 self.inputEventHandlers["reviewSpellCurrentItemHandler"] = \ 263 input_event.InputEventHandler( 264 Script.reviewSpellCurrentItem, 265 cmdnames.REVIEW_SPELL_CURRENT_ITEM) 266 267 self.inputEventHandlers["reviewPhoneticCurrentItemHandler"] = \ 268 input_event.InputEventHandler( 269 Script.reviewPhoneticCurrentItem, 270 cmdnames.REVIEW_PHONETIC_CURRENT_ITEM) 271 272 self.inputEventHandlers["reviewNextItemHandler"] = \ 273 input_event.InputEventHandler( 274 Script.reviewNextItem, 275 cmdnames.REVIEW_NEXT_ITEM) 276 277 self.inputEventHandlers["reviewCurrentAccessibleHandler"] = \ 278 input_event.InputEventHandler( 279 Script.reviewCurrentAccessible, 280 cmdnames.REVIEW_CURRENT_ACCESSIBLE) 281 282 self.inputEventHandlers["reviewBelowHandler"] = \ 283 input_event.InputEventHandler( 284 Script.reviewBelow, 285 cmdnames.REVIEW_BELOW) 286 287 self.inputEventHandlers["reviewPreviousCharacterHandler"] = \ 288 input_event.InputEventHandler( 289 Script.reviewPreviousCharacter, 290 cmdnames.REVIEW_PREVIOUS_CHARACTER) 291 292 self.inputEventHandlers["reviewEndOfLineHandler"] = \ 293 input_event.InputEventHandler( 294 Script.reviewEndOfLine, 295 cmdnames.REVIEW_END_OF_LINE) 296 297 self.inputEventHandlers["reviewBottomLeftHandler"] = \ 298 input_event.InputEventHandler( 299 Script.reviewBottomLeft, 300 cmdnames.REVIEW_BOTTOM_LEFT) 301 302 self.inputEventHandlers["reviewCurrentCharacterHandler"] = \ 303 input_event.InputEventHandler( 304 Script.reviewCurrentCharacter, 305 cmdnames.REVIEW_CURRENT_CHARACTER) 306 307 self.inputEventHandlers["reviewSpellCurrentCharacterHandler"] = \ 308 input_event.InputEventHandler( 309 Script.reviewSpellCurrentCharacter, 310 cmdnames.REVIEW_SPELL_CURRENT_CHARACTER) 311 312 self.inputEventHandlers["reviewUnicodeCurrentCharacterHandler"] = \ 313 input_event.InputEventHandler( 314 Script.reviewUnicodeCurrentCharacter, 315 cmdnames.REVIEW_UNICODE_CURRENT_CHARACTER) 316 317 self.inputEventHandlers["reviewNextCharacterHandler"] = \ 318 input_event.InputEventHandler( 319 Script.reviewNextCharacter, 320 cmdnames.REVIEW_NEXT_CHARACTER) 321 322 self.inputEventHandlers["flatReviewCopyHandler"] = \ 323 input_event.InputEventHandler( 324 Script.flatReviewCopy, 325 cmdnames.FLAT_REVIEW_COPY) 326 327 self.inputEventHandlers["flatReviewAppendHandler"] = \ 328 input_event.InputEventHandler( 329 Script.flatReviewAppend, 330 cmdnames.FLAT_REVIEW_APPEND) 331 332 self.inputEventHandlers["toggleTableCellReadModeHandler"] = \ 333 input_event.InputEventHandler( 334 Script.toggleTableCellReadMode, 335 cmdnames.TOGGLE_TABLE_CELL_READ_MODE) 336 337 self.inputEventHandlers["readCharAttributesHandler"] = \ 338 input_event.InputEventHandler( 339 Script.readCharAttributes, 340 cmdnames.READ_CHAR_ATTRIBUTES) 341 342 self.inputEventHandlers["panBrailleLeftHandler"] = \ 343 input_event.InputEventHandler( 344 Script.panBrailleLeft, 345 cmdnames.PAN_BRAILLE_LEFT, 346 False) # Do not enable learn mode for this action 347 348 self.inputEventHandlers["panBrailleRightHandler"] = \ 349 input_event.InputEventHandler( 350 Script.panBrailleRight, 351 cmdnames.PAN_BRAILLE_RIGHT, 352 False) # Do not enable learn mode for this action 353 354 self.inputEventHandlers["goBrailleHomeHandler"] = \ 355 input_event.InputEventHandler( 356 Script.goBrailleHome, 357 cmdnames.GO_BRAILLE_HOME) 358 359 self.inputEventHandlers["contractedBrailleHandler"] = \ 360 input_event.InputEventHandler( 361 Script.setContractedBraille, 362 cmdnames.SET_CONTRACTED_BRAILLE) 363 364 self.inputEventHandlers["processRoutingKeyHandler"] = \ 365 input_event.InputEventHandler( 366 Script.processRoutingKey, 367 cmdnames.PROCESS_ROUTING_KEY) 368 369 self.inputEventHandlers["processBrailleCutBeginHandler"] = \ 370 input_event.InputEventHandler( 371 Script.processBrailleCutBegin, 372 cmdnames.PROCESS_BRAILLE_CUT_BEGIN) 373 374 self.inputEventHandlers["processBrailleCutLineHandler"] = \ 375 input_event.InputEventHandler( 376 Script.processBrailleCutLine, 377 cmdnames.PROCESS_BRAILLE_CUT_LINE) 378 379 self.inputEventHandlers["enterLearnModeHandler"] = \ 380 input_event.InputEventHandler( 381 Script.enterLearnMode, 382 cmdnames.ENTER_LEARN_MODE) 383 384 self.inputEventHandlers["decreaseSpeechRateHandler"] = \ 385 input_event.InputEventHandler( 386 speech.decreaseSpeechRate, 387 cmdnames.DECREASE_SPEECH_RATE) 388 389 self.inputEventHandlers["increaseSpeechRateHandler"] = \ 390 input_event.InputEventHandler( 391 speech.increaseSpeechRate, 392 cmdnames.INCREASE_SPEECH_RATE) 393 394 self.inputEventHandlers["decreaseSpeechPitchHandler"] = \ 395 input_event.InputEventHandler( 396 speech.decreaseSpeechPitch, 397 cmdnames.DECREASE_SPEECH_PITCH) 398 399 self.inputEventHandlers["increaseSpeechPitchHandler"] = \ 400 input_event.InputEventHandler( 401 speech.increaseSpeechPitch, 402 cmdnames.INCREASE_SPEECH_PITCH) 403 404 self.inputEventHandlers["decreaseSpeechVolumeHandler"] = \ 405 input_event.InputEventHandler( 406 speech.decreaseSpeechVolume, 407 cmdnames.DECREASE_SPEECH_VOLUME) 408 409 self.inputEventHandlers["increaseSpeechVolumeHandler"] = \ 410 input_event.InputEventHandler( 411 speech.increaseSpeechVolume, 412 cmdnames.INCREASE_SPEECH_VOLUME) 413 414 self.inputEventHandlers["shutdownHandler"] = \ 415 input_event.InputEventHandler( 416 orca.quitOrca, 417 cmdnames.QUIT_ORCA) 418 419 self.inputEventHandlers["preferencesSettingsHandler"] = \ 420 input_event.InputEventHandler( 421 orca.showPreferencesGUI, 422 cmdnames.SHOW_PREFERENCES_GUI) 423 424 self.inputEventHandlers["appPreferencesSettingsHandler"] = \ 425 input_event.InputEventHandler( 426 orca.showAppPreferencesGUI, 427 cmdnames.SHOW_APP_PREFERENCES_GUI) 428 429 self.inputEventHandlers["toggleSilenceSpeechHandler"] = \ 430 input_event.InputEventHandler( 431 Script.toggleSilenceSpeech, 432 cmdnames.TOGGLE_SPEECH) 433 434 self.inputEventHandlers["toggleSpeechVerbosityHandler"] = \ 435 input_event.InputEventHandler( 436 Script.toggleSpeechVerbosity, 437 cmdnames.TOGGLE_SPEECH_VERBOSITY) 438 439 self.inputEventHandlers[ \ 440 "toggleSpeakingIndentationJustificationHandler"] = \ 441 input_event.InputEventHandler( 442 Script.toggleSpeakingIndentationJustification, 443 cmdnames.TOGGLE_SPOKEN_INDENTATION_AND_JUSTIFICATION) 444 445 self.inputEventHandlers[ \ 446 "changeNumberStyleHandler"] = \ 447 input_event.InputEventHandler( 448 Script.changeNumberStyle, 449 cmdnames.CHANGE_NUMBER_STYLE) 450 451 self.inputEventHandlers["cycleSpeakingPunctuationLevelHandler"] = \ 452 input_event.InputEventHandler( 453 Script.cycleSpeakingPunctuationLevel, 454 cmdnames.CYCLE_PUNCTUATION_LEVEL) 455 456 self.inputEventHandlers["cycleSettingsProfileHandler"] = \ 457 input_event.InputEventHandler( 458 Script.cycleSettingsProfile, 459 cmdnames.CYCLE_SETTINGS_PROFILE) 460 461 self.inputEventHandlers["cycleCapitalizationStyleHandler"] = \ 462 input_event.InputEventHandler( 463 Script.cycleCapitalizationStyle, 464 cmdnames.CYCLE_CAPITALIZATION_STYLE) 465 466 self.inputEventHandlers["cycleKeyEchoHandler"] = \ 467 input_event.InputEventHandler( 468 Script.cycleKeyEcho, 469 cmdnames.CYCLE_KEY_ECHO) 470 471 self.inputEventHandlers["cycleDebugLevelHandler"] = \ 472 input_event.InputEventHandler( 473 Script.cycleDebugLevel, 474 cmdnames.CYCLE_DEBUG_LEVEL) 475 476 self.inputEventHandlers["goToPrevBookmark"] = \ 477 input_event.InputEventHandler( 478 Script.goToPrevBookmark, 479 cmdnames.BOOKMARK_GO_TO_PREVIOUS) 480 481 self.inputEventHandlers["goToBookmark"] = \ 482 input_event.InputEventHandler( 483 Script.goToBookmark, 484 cmdnames.BOOKMARK_GO_TO) 485 486 self.inputEventHandlers["goToNextBookmark"] = \ 487 input_event.InputEventHandler( 488 Script.goToNextBookmark, 489 cmdnames.BOOKMARK_GO_TO_NEXT) 490 491 self.inputEventHandlers["addBookmark"] = \ 492 input_event.InputEventHandler( 493 Script.addBookmark, 494 cmdnames.BOOKMARK_ADD) 495 496 self.inputEventHandlers["saveBookmarks"] = \ 497 input_event.InputEventHandler( 498 Script.saveBookmarks, 499 cmdnames.BOOKMARK_SAVE) 500 501 self.inputEventHandlers["toggleMouseReviewHandler"] = \ 502 input_event.InputEventHandler( 503 mouse_review.reviewer.toggle, 504 cmdnames.MOUSE_REVIEW_TOGGLE) 505 506 self.inputEventHandlers["presentTimeHandler"] = \ 507 input_event.InputEventHandler( 508 Script.presentTime, 509 cmdnames.PRESENT_CURRENT_TIME) 510 511 self.inputEventHandlers["presentDateHandler"] = \ 512 input_event.InputEventHandler( 513 Script.presentDate, 514 cmdnames.PRESENT_CURRENT_DATE) 515 516 self.inputEventHandlers["bypassNextCommandHandler"] = \ 517 input_event.InputEventHandler( 518 Script.bypassNextCommand, 519 cmdnames.BYPASS_NEXT_COMMAND) 520 521 self.inputEventHandlers["presentSizeAndPositionHandler"] = \ 522 input_event.InputEventHandler( 523 Script.presentSizeAndPosition, 524 cmdnames.PRESENT_SIZE_AND_POSITION) 525 526 self.inputEventHandlers.update(notification_messages.inputEventHandlers) 527 528 def getInputEventHandlerKey(self, inputEventHandler): 529 """Returns the name of the key that contains an inputEventHadler 530 passed as argument 531 """ 532 533 for keyName, handler in self.inputEventHandlers.items(): 534 if handler == inputEventHandler: 535 return keyName 536 537 return None 538 539 def getListeners(self): 540 """Sets up the AT-SPI event listeners for this script. 541 """ 542 listeners = script.Script.getListeners(self) 543 listeners["focus:"] = \ 544 self.onFocus 545 #listeners["keyboard:modifiers"] = \ 546 # self.noOp 547 listeners["document:reload"] = \ 548 self.onDocumentReload 549 listeners["document:load-complete"] = \ 550 self.onDocumentLoadComplete 551 listeners["document:load-stopped"] = \ 552 self.onDocumentLoadStopped 553 listeners["mouse:button"] = \ 554 self.onMouseButton 555 listeners["object:property-change:accessible-name"] = \ 556 self.onNameChanged 557 listeners["object:property-change:accessible-description"] = \ 558 self.onDescriptionChanged 559 listeners["object:text-caret-moved"] = \ 560 self.onCaretMoved 561 listeners["object:text-changed:delete"] = \ 562 self.onTextDeleted 563 listeners["object:text-changed:insert"] = \ 564 self.onTextInserted 565 listeners["object:active-descendant-changed"] = \ 566 self.onActiveDescendantChanged 567 listeners["object:children-changed:add"] = \ 568 self.onChildrenAdded 569 listeners["object:children-changed:remove"] = \ 570 self.onChildrenRemoved 571 listeners["object:state-changed:active"] = \ 572 self.onActiveChanged 573 listeners["object:state-changed:busy"] = \ 574 self.onBusyChanged 575 listeners["object:state-changed:focused"] = \ 576 self.onFocusedChanged 577 listeners["object:state-changed:showing"] = \ 578 self.onShowingChanged 579 listeners["object:state-changed:checked"] = \ 580 self.onCheckedChanged 581 listeners["object:state-changed:pressed"] = \ 582 self.onPressedChanged 583 listeners["object:state-changed:indeterminate"] = \ 584 self.onIndeterminateChanged 585 listeners["object:state-changed:expanded"] = \ 586 self.onExpandedChanged 587 listeners["object:state-changed:selected"] = \ 588 self.onSelectedChanged 589 listeners["object:state-changed:sensitive"] = \ 590 self.onSensitiveChanged 591 listeners["object:text-attributes-changed"] = \ 592 self.onTextAttributesChanged 593 listeners["object:text-selection-changed"] = \ 594 self.onTextSelectionChanged 595 listeners["object:selection-changed"] = \ 596 self.onSelectionChanged 597 listeners["object:property-change:accessible-value"] = \ 598 self.onValueChanged 599 listeners["object:value-changed"] = \ 600 self.onValueChanged 601 listeners["object:column-reordered"] = \ 602 self.onColumnReordered 603 listeners["object:row-reordered"] = \ 604 self.onRowReordered 605 listeners["window:activate"] = \ 606 self.onWindowActivated 607 listeners["window:deactivate"] = \ 608 self.onWindowDeactivated 609 listeners["window:create"] = \ 610 self.onWindowCreated 611 listeners["window:destroy"] = \ 612 self.onWindowDestroyed 613 614 return listeners 615 616 def __getDesktopBindings(self): 617 """Returns an instance of keybindings.KeyBindings that use the 618 numeric keypad for focus tracking and flat review. 619 """ 620 621 import orca.desktop_keyboardmap as desktop_keyboardmap 622 keyBindings = keybindings.KeyBindings() 623 keyBindings.load(desktop_keyboardmap.keymap, self.inputEventHandlers) 624 return keyBindings 625 626 def __getLaptopBindings(self): 627 """Returns an instance of keybindings.KeyBindings that use the 628 the main keyboard keys for focus tracking and flat review. 629 """ 630 631 import orca.laptop_keyboardmap as laptop_keyboardmap 632 keyBindings = keybindings.KeyBindings() 633 keyBindings.load(laptop_keyboardmap.keymap, self.inputEventHandlers) 634 return keyBindings 635 636 def getKeyBindings(self): 637 """Defines the key bindings for this script. 638 639 Returns an instance of keybindings.KeyBindings. 640 """ 641 642 keyBindings = script.Script.getKeyBindings(self) 643 644 bindings = self.getDefaultKeyBindings() 645 for keyBinding in bindings.keyBindings: 646 keyBindings.add(keyBinding) 647 648 bindings = self.getToolkitKeyBindings() 649 for keyBinding in bindings.keyBindings: 650 keyBindings.add(keyBinding) 651 652 bindings = self.getAppKeyBindings() 653 for keyBinding in bindings.keyBindings: 654 keyBindings.add(keyBinding) 655 656 try: 657 keyBindings = _settingsManager.overrideKeyBindings(self, keyBindings) 658 except: 659 msg = 'ERROR: Exception when overriding keybindings in %s' % self 660 debug.println(debug.LEVEL_WARNING, msg, True) 661 debug.printException(debug.LEVEL_WARNING) 662 663 return keyBindings 664 665 def getDefaultKeyBindings(self): 666 """Returns the default script's keybindings, i.e. without any of 667 the toolkit or application specific commands added.""" 668 669 keyBindings = keybindings.KeyBindings() 670 671 layout = _settingsManager.getSetting('keyboardLayout') 672 if layout == settings.GENERAL_KEYBOARD_LAYOUT_DESKTOP: 673 for keyBinding in self.__getDesktopBindings().keyBindings: 674 keyBindings.add(keyBinding) 675 else: 676 for keyBinding in self.__getLaptopBindings().keyBindings: 677 keyBindings.add(keyBinding) 678 679 import orca.common_keyboardmap as common_keyboardmap 680 keyBindings.load(common_keyboardmap.keymap, self.inputEventHandlers) 681 682 return keyBindings 683 684 def getBrailleBindings(self): 685 """Defines the braille bindings for this script. 686 687 Returns a dictionary where the keys are BrlTTY commands and the 688 values are InputEventHandler instances. 689 """ 690 691 msg = 'DEFAULT: Getting braille bindings.' 692 debug.println(debug.LEVEL_INFO, msg, True) 693 694 brailleBindings = script.Script.getBrailleBindings(self) 695 try: 696 brailleBindings[braille.brlapi.KEY_CMD_HWINLT] = \ 697 self.inputEventHandlers["panBrailleLeftHandler"] 698 brailleBindings[braille.brlapi.KEY_CMD_FWINLT] = \ 699 self.inputEventHandlers["panBrailleLeftHandler"] 700 brailleBindings[braille.brlapi.KEY_CMD_FWINLTSKIP] = \ 701 self.inputEventHandlers["panBrailleLeftHandler"] 702 brailleBindings[braille.brlapi.KEY_CMD_HWINRT] = \ 703 self.inputEventHandlers["panBrailleRightHandler"] 704 brailleBindings[braille.brlapi.KEY_CMD_FWINRT] = \ 705 self.inputEventHandlers["panBrailleRightHandler"] 706 brailleBindings[braille.brlapi.KEY_CMD_FWINRTSKIP] = \ 707 self.inputEventHandlers["panBrailleRightHandler"] 708 brailleBindings[braille.brlapi.KEY_CMD_LNUP] = \ 709 self.inputEventHandlers["reviewAboveHandler"] 710 brailleBindings[braille.brlapi.KEY_CMD_LNDN] = \ 711 self.inputEventHandlers["reviewBelowHandler"] 712 brailleBindings[braille.brlapi.KEY_CMD_FREEZE] = \ 713 self.inputEventHandlers["toggleFlatReviewModeHandler"] 714 brailleBindings[braille.brlapi.KEY_CMD_TOP_LEFT] = \ 715 self.inputEventHandlers["reviewHomeHandler"] 716 brailleBindings[braille.brlapi.KEY_CMD_BOT_LEFT] = \ 717 self.inputEventHandlers["reviewBottomLeftHandler"] 718 brailleBindings[braille.brlapi.KEY_CMD_HOME] = \ 719 self.inputEventHandlers["goBrailleHomeHandler"] 720 brailleBindings[braille.brlapi.KEY_CMD_SIXDOTS] = \ 721 self.inputEventHandlers["contractedBrailleHandler"] 722 brailleBindings[braille.brlapi.KEY_CMD_ROUTE] = \ 723 self.inputEventHandlers["processRoutingKeyHandler"] 724 brailleBindings[braille.brlapi.KEY_CMD_CUTBEGIN] = \ 725 self.inputEventHandlers["processBrailleCutBeginHandler"] 726 brailleBindings[braille.brlapi.KEY_CMD_CUTLINE] = \ 727 self.inputEventHandlers["processBrailleCutLineHandler"] 728 except AttributeError: 729 msg = 'DEFAULT: Braille bindings unavailable in %s' % self 730 debug.println(debug.LEVEL_INFO, msg, True) 731 except: 732 msg = 'ERROR: Exception getting braille bindings in %s' % self 733 debug.println(debug.LEVEL_INFO, msg, True) 734 debug.printException(debug.LEVEL_CONFIGURATION) 735 736 msg = 'DEFAULT: Finished getting braille bindings.' 737 debug.println(debug.LEVEL_INFO, msg, True) 738 739 return brailleBindings 740 741 def deactivate(self): 742 """Called when this script is deactivated.""" 743 744 self._inSayAll = False 745 self._sayAllIsInterrupted = False 746 self.pointOfReference = {} 747 748 self.removeKeyGrabs() 749 750 def getEnabledKeyBindings(self): 751 """ Returns the key bindings that are currently active. """ 752 return self.getKeyBindings().getBoundBindings() 753 754 def addKeyGrabs(self): 755 """ Sets up the key grabs currently needed by this script. """ 756 if orca_state.device is None: 757 return 758 msg = "INFO: adding key grabs" 759 debug.println(debug.LEVEL_INFO, msg, True) 760 bound = self.getEnabledKeyBindings() 761 for b in bound: 762 for id in orca.addKeyGrab(b): 763 self.grab_ids.append(id) 764 765 def removeKeyGrabs(self): 766 """ Removes this script's AT-SPI key grabs. """ 767 msg = "INFO: removing key grabs" 768 debug.println(debug.LEVEL_INFO, msg, True) 769 for id in self.grab_ids: 770 orca.removeKeyGrab(id) 771 self.grab_ids = [] 772 773 def refreshKeyGrabs(self): 774 """ Refreshes the enabled key grabs for this script. """ 775 # TODO: Should probably avoid removing key grabs and re-adding them. 776 # Otherwise, a key could conceivably leak through while the script is 777 # in the process of updating the bindings. 778 self.removeKeyGrabs() 779 self.addKeyGrabs() 780 781 def registerEventListeners(self): 782 super().registerEventListeners() 783 self.utilities.connectToClipboard() 784 785 def deregisterEventListeners(self): 786 super().deregisterEventListeners() 787 self.utilities.disconnectFromClipboard() 788 789 def _saveFocusedObjectInfo(self, obj): 790 """Saves some basic information about obj. Note that this method is 791 intended to be called primarily (if not only) by locusOfFocusChanged(). 792 It is expected that accessible event callbacks will update the point 793 of reference data specific to that event. The goal here is to weed 794 out duplicate events.""" 795 796 if not obj: 797 return 798 799 try: 800 role = obj.getRole() 801 state = obj.getState() 802 name = obj.name 803 description = obj.description 804 except: 805 return 806 807 # We want to save the name because some apps and toolkits emit name 808 # changes after the focus or selection has changed, even though the 809 # name has not. 810 names = self.pointOfReference.get('names', {}) 811 names[hash(obj)] = name 812 if orca_state.activeWindow: 813 try: 814 names[hash(orca_state.activeWindow)] = orca_state.activeWindow.name 815 except: 816 msg = "ERROR: Exception getting name for %s" % orca_state.activeWindow 817 debug.println(debug.LEVEL_INFO, msg, True) 818 819 self.pointOfReference['names'] = names 820 821 descriptions = self.pointOfReference.get('descriptions', {}) 822 descriptions[hash(obj)] = description 823 self.pointOfReference['descriptions'] = descriptions 824 825 # We want to save the offset for text objects because some apps and 826 # toolkits emit caret-moved events immediately after a text object 827 # gains focus, even though the caret has not actually moved. 828 try: 829 text = obj.queryText() 830 caretOffset = text.caretOffset 831 except: 832 pass 833 else: 834 self._saveLastCursorPosition(obj, max(0, caretOffset)) 835 self.utilities.updateCachedTextSelection(obj) 836 837 # We want to save the current row and column of a newly focused 838 # or selected table cell so that on subsequent cell focus/selection 839 # we only present the changed location. 840 row, column = self.utilities.coordinatesForCell(obj) 841 self.pointOfReference['lastColumn'] = column 842 self.pointOfReference['lastRow'] = row 843 844 self.pointOfReference['checkedChange'] = \ 845 hash(obj), state.contains(pyatspi.STATE_CHECKED) 846 self.pointOfReference['selectedChange'] = \ 847 hash(obj), state.contains(pyatspi.STATE_SELECTED) 848 849 def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus): 850 """Called when the visual object with focus changes. 851 852 Arguments: 853 - event: if not None, the Event that caused the change 854 - oldLocusOfFocus: Accessible that is the old locus of focus 855 - newLocusOfFocus: Accessible that is the new locus of focus 856 """ 857 858 self.utilities.presentFocusChangeReason() 859 860 if not newLocusOfFocus: 861 orca_state.noFocusTimeStamp = time.time() 862 return 863 864 if newLocusOfFocus.getState().contains(pyatspi.STATE_DEFUNCT): 865 return 866 867 if self.utilities.isSameObject(oldLocusOfFocus, newLocusOfFocus): 868 return 869 870 try: 871 if self.findCommandRun: 872 # Then the Orca Find dialog has just given up focus 873 # to the original window. We don't want to speak 874 # the window title, current line, etc. 875 return 876 except: 877 pass 878 879 if self.flatReviewContext: 880 self.toggleFlatReviewMode() 881 882 topLevel = self.utilities.topLevelObject(newLocusOfFocus) 883 if orca_state.activeWindow != topLevel: 884 orca_state.activeWindow = topLevel 885 self.windowActivateTime = time.time() 886 887 self.updateBraille(newLocusOfFocus) 888 889 shouldNotInterrupt = \ 890 self.windowActivateTime and time.time() - self.windowActivateTime < 1 891 892 utterances = self.speechGenerator.generateSpeech( 893 newLocusOfFocus, 894 priorObj=oldLocusOfFocus) 895 896 speech.speak(utterances, interrupt=not shouldNotInterrupt) 897 orca.emitRegionChanged(newLocusOfFocus) 898 self._saveFocusedObjectInfo(newLocusOfFocus) 899 900 def activate(self): 901 """Called when this script is activated.""" 902 903 msg = 'DEFAULT: activating script for %s' % self.app 904 debug.println(debug.LEVEL_INFO, msg, True) 905 906 _settingsManager.loadAppSettings(self) 907 braille.checkBrailleSetting() 908 braille.setupKeyRanges(self.brailleBindings.keys()) 909 speech.checkSpeechSetting() 910 speech.updatePunctuationLevel() 911 speech.updateCapitalizationStyle() 912 913 # Gtk 4 requrns "GTK", while older versions return "gtk" 914 # TODO: move this to a toolkit-specific script 915 if self.app is not None and self.app.toolkitName == "GTK" and self.app.toolkitVersion > "4": 916 orca.setKeyHandling(True) 917 else: 918 orca.setKeyHandling(False) 919 920 self.addKeyGrabs() 921 922 msg = 'DEFAULT: Script for %s activated' % self.app 923 debug.println(debug.LEVEL_INFO, msg, True) 924 925 def updateBraille(self, obj, **args): 926 """Updates the braille display to show the give object. 927 928 Arguments: 929 - obj: the Accessible 930 """ 931 932 if not _settingsManager.getSetting('enableBraille') \ 933 and not _settingsManager.getSetting('enableBrailleMonitor'): 934 debug.println(debug.LEVEL_INFO, "BRAILLE: update disabled", True) 935 return 936 937 if not obj: 938 return 939 940 result, focusedRegion = self.brailleGenerator.generateBraille(obj, **args) 941 if not result: 942 return 943 944 self.clearBraille() 945 line = self.getNewBrailleLine() 946 braille.addLine(line) 947 self.addBrailleRegionsToLine(result, line) 948 949 extraRegion = args.get('extraRegion') 950 if extraRegion: 951 self.addBrailleRegionToLine(extraRegion, line) 952 self.setBrailleFocus(extraRegion) 953 else: 954 self.setBrailleFocus(focusedRegion) 955 956 self.refreshBraille(True) 957 958 ######################################################################## 959 # # 960 # INPUT EVENT HANDLERS (AKA ORCA COMMANDS) # 961 # # 962 ######################################################################## 963 964 def bypassNextCommand(self, inputEvent=None): 965 """Causes the next keyboard command to be ignored by Orca 966 and passed along to the current application. 967 968 Returns True to indicate the input event has been consumed. 969 """ 970 971 self.presentMessage(messages.BYPASS_MODE_ENABLED) 972 orca_state.bypassNextCommand = True 973 self.removeKeyGrabs() 974 return True 975 976 def enterLearnMode(self, inputEvent=None): 977 """Turns learn mode on. The user must press the escape key to exit 978 learn mode. 979 980 Returns True to indicate the input event has been consumed. 981 """ 982 983 if orca_state.learnModeEnabled: 984 return True 985 986 self.presentMessage(messages.VERSION) 987 self.speakMessage(messages.LEARN_MODE_START_SPEECH) 988 self.displayBrailleMessage(messages.LEARN_MODE_START_BRAILLE) 989 orca_state.learnModeEnabled = True 990 if orca_state.device is not None: 991 Atspi.Device.grab_keyboard(orca_state.device) 992 return True 993 994 def exitLearnMode(self, inputEvent=None): 995 """Turns learn mode off. 996 997 Returns True to indicate the input event has been consumed. 998 """ 999 1000 if not orca_state.learnModeEnabled: 1001 return False 1002 1003 if isinstance(inputEvent, input_event.KeyboardEvent) \ 1004 and not inputEvent.event_string == 'Escape': 1005 return False 1006 1007 self.presentMessage(messages.LEARN_MODE_STOP) 1008 orca_state.learnModeEnabled = False 1009 if orca_state.device is not None: 1010 Atspi.Device.ungrab_keyboard(orca_state.device) 1011 return True 1012 1013 def showHelp(self, inputEvent=None): 1014 return orca.helpForOrca() 1015 1016 def listNotifications(self, inputEvent=None): 1017 if inputEvent is None: 1018 inputEvent = orca_state.lastNonModifierKeyEvent 1019 1020 return notification_messages.listNotificationMessages(self, inputEvent) 1021 1022 def listOrcaShortcuts(self, inputEvent=None): 1023 """Shows a simple gui listing Orca's bound commands.""" 1024 1025 if inputEvent is None: 1026 inputEvent = orca_state.lastNonModifierKeyEvent 1027 1028 if not inputEvent or inputEvent.event_string == "F2": 1029 bound = self.getDefaultKeyBindings().getBoundBindings() 1030 title = messages.shortcutsFoundOrca(len(bound)) 1031 else: 1032 try: 1033 appName = self.app.name 1034 except AttributeError: 1035 appName = messages.APPLICATION_NO_NAME 1036 1037 bound = self.getAppKeyBindings().getBoundBindings() 1038 bound.extend(self.getToolkitKeyBindings().getBoundBindings()) 1039 title = messages.shortcutsFoundApp(len(bound), appName) 1040 1041 if not bound: 1042 self.presentMessage(title) 1043 return True 1044 1045 self.exitLearnMode() 1046 1047 rows = [(kb.handler.function, 1048 kb.handler.description, 1049 kb.asString()) for kb in bound] 1050 sorted(rows, key=lambda cmd: cmd[2]) 1051 1052 header1 = guilabels.KB_HEADER_FUNCTION 1053 header2 = guilabels.KB_HEADER_KEY_BINDING 1054 commandlist.showUI(title, ("", header1, header2), rows, False) 1055 return True 1056 1057 def findNext(self, inputEvent): 1058 """Searches forward for the next instance of the string 1059 searched for via the Orca Find dialog. Other than direction 1060 and the starting point, the search options initially specified 1061 (case sensitivity, window wrap, and full/partial match) are 1062 preserved. 1063 """ 1064 1065 lastQuery = find.getLastQuery() 1066 if lastQuery: 1067 lastQuery.searchBackwards = False 1068 lastQuery.startAtTop = False 1069 self.find(lastQuery) 1070 else: 1071 orca.showFindGUI() 1072 1073 def findPrevious(self, inputEvent): 1074 """Searches backwards for the next instance of the string 1075 searched for via the Orca Find dialog. Other than direction 1076 and the starting point, the search options initially specified 1077 (case sensitivity, window wrap, and full/or partial match) are 1078 preserved. 1079 """ 1080 1081 lastQuery = find.getLastQuery() 1082 if lastQuery: 1083 lastQuery.searchBackwards = True 1084 lastQuery.startAtTop = False 1085 self.find(lastQuery) 1086 else: 1087 orca.showFindGUI() 1088 1089 def addBookmark(self, inputEvent): 1090 """ Add an in-page accessible object bookmark for this key. 1091 Delegates to Bookmark.addBookmark """ 1092 bookmarks = self.getBookmarks() 1093 bookmarks.addBookmark(inputEvent) 1094 1095 def goToBookmark(self, inputEvent): 1096 """ Go to the bookmark indexed by inputEvent.hw_code. Delegates to 1097 Bookmark.goToBookmark """ 1098 bookmarks = self.getBookmarks() 1099 bookmarks.goToBookmark(inputEvent) 1100 1101 def goToNextBookmark(self, inputEvent): 1102 """ Go to the next bookmark location. If no bookmark has yet to be 1103 selected, the first bookmark will be used. Delegates to 1104 Bookmark.goToNextBookmark """ 1105 bookmarks = self.getBookmarks() 1106 bookmarks.goToNextBookmark(inputEvent) 1107 1108 def goToPrevBookmark(self, inputEvent): 1109 """ Go to the previous bookmark location. If no bookmark has yet to 1110 be selected, the first bookmark will be used. Delegates to 1111 Bookmark.goToPrevBookmark """ 1112 bookmarks = self.getBookmarks() 1113 bookmarks.goToPrevBookmark(inputEvent) 1114 1115 def saveBookmarks(self, inputEvent): 1116 """ Save the bookmarks for this script. Delegates to 1117 Bookmark.saveBookmarks """ 1118 bookmarks = self.getBookmarks() 1119 bookmarks.saveBookmarks(inputEvent) 1120 1121 def panBrailleLeft(self, inputEvent=None, panAmount=0): 1122 """Pans the braille display to the left. If panAmount is non-zero, 1123 the display is panned by that many cells. If it is 0, the display 1124 is panned one full display width. In flat review mode, panning 1125 beyond the beginning will take you to the end of the previous line. 1126 1127 In focus tracking mode, the cursor stays at its logical position. 1128 In flat review mode, the review cursor moves to character 1129 associated with cell 0.""" 1130 1131 if self.flatReviewContext: 1132 if self.isBrailleBeginningShowing(): 1133 self.flatReviewContext.goBegin(flat_review.Context.LINE) 1134 self.reviewPreviousCharacter(inputEvent) 1135 else: 1136 self.panBrailleInDirection(panAmount, panToLeft=True) 1137 1138 self._setFlatReviewContextToBeginningOfBrailleDisplay() 1139 self.targetCursorCell = 1 1140 self.updateBrailleReview(self.targetCursorCell) 1141 elif self.isBrailleBeginningShowing() and orca_state.locusOfFocus \ 1142 and self.utilities.isTextArea(orca_state.locusOfFocus): 1143 1144 # If we're at the beginning of a line of a multiline text 1145 # area, then force it's caret to the end of the previous 1146 # line. The assumption here is that we're currently 1147 # viewing the line that has the caret -- which is a pretty 1148 # good assumption for focus tacking mode. When we set the 1149 # caret position, we will get a caret event, which will 1150 # then update the braille. 1151 # 1152 text = orca_state.locusOfFocus.queryText() 1153 [lineString, startOffset, endOffset] = text.getTextAtOffset( 1154 text.caretOffset, 1155 pyatspi.TEXT_BOUNDARY_LINE_START) 1156 movedCaret = False 1157 if startOffset > 0: 1158 movedCaret = text.setCaretOffset(startOffset - 1) 1159 1160 # If we didn't move the caret and we're in a terminal, we 1161 # jump into flat review to review the text. See 1162 # http://bugzilla.gnome.org/show_bug.cgi?id=482294. 1163 # 1164 if (not movedCaret) \ 1165 and (orca_state.locusOfFocus.getRole() \ 1166 == pyatspi.ROLE_TERMINAL): 1167 context = self.getFlatReviewContext() 1168 context.goBegin(flat_review.Context.LINE) 1169 self.reviewPreviousCharacter(inputEvent) 1170 else: 1171 self.panBrailleInDirection(panAmount, panToLeft=True) 1172 # We might be panning through a flashed message. 1173 # 1174 braille.resetFlashTimer() 1175 self.refreshBraille(False, stopFlash=False) 1176 1177 return True 1178 1179 def panBrailleLeftOneChar(self, inputEvent=None): 1180 """Nudges the braille display one character to the left. 1181 1182 In focus tracking mode, the cursor stays at its logical position. 1183 In flat review mode, the review cursor moves to character 1184 associated with cell 0.""" 1185 1186 self.panBrailleLeft(inputEvent, 1) 1187 1188 def panBrailleRight(self, inputEvent=None, panAmount=0): 1189 """Pans the braille display to the right. If panAmount is non-zero, 1190 the display is panned by that many cells. If it is 0, the display 1191 is panned one full display width. In flat review mode, panning 1192 beyond the end will take you to the beginning of the next line. 1193 1194 In focus tracking mode, the cursor stays at its logical position. 1195 In flat review mode, the review cursor moves to character 1196 associated with cell 0.""" 1197 1198 if self.flatReviewContext: 1199 if self.isBrailleEndShowing(): 1200 self.flatReviewContext.goEnd(flat_review.Context.LINE) 1201 # Reviewing the next character also updates the braille output and refreshes the display. 1202 self.reviewNextCharacter(inputEvent) 1203 return 1204 self.panBrailleInDirection(panAmount, panToLeft=False) 1205 self._setFlatReviewContextToBeginningOfBrailleDisplay() 1206 self.targetCursorCell = 1 1207 self.updateBrailleReview(self.targetCursorCell) 1208 elif self.isBrailleEndShowing() and orca_state.locusOfFocus \ 1209 and self.utilities.isTextArea(orca_state.locusOfFocus): 1210 # If we're at the end of a line of a multiline text area, then 1211 # force it's caret to the beginning of the next line. The 1212 # assumption here is that we're currently viewing the line that 1213 # has the caret -- which is a pretty good assumption for focus 1214 # tacking mode. When we set the caret position, we will get a 1215 # caret event, which will then update the braille. 1216 # 1217 text = orca_state.locusOfFocus.queryText() 1218 [lineString, startOffset, endOffset] = text.getTextAtOffset( 1219 text.caretOffset, 1220 pyatspi.TEXT_BOUNDARY_LINE_START) 1221 if endOffset < text.characterCount: 1222 text.setCaretOffset(endOffset) 1223 else: 1224 self.panBrailleInDirection(panAmount, panToLeft=False) 1225 # We might be panning through a flashed message. 1226 # 1227 braille.resetFlashTimer() 1228 self.refreshBraille(False, stopFlash=False) 1229 1230 return True 1231 1232 def panBrailleRightOneChar(self, inputEvent=None): 1233 """Nudges the braille display one character to the right. 1234 1235 In focus tracking mode, the cursor stays at its logical position. 1236 In flat review mode, the review cursor moves to character 1237 associated with cell 0.""" 1238 1239 self.panBrailleRight(inputEvent, 1) 1240 1241 def goBrailleHome(self, inputEvent=None): 1242 """Returns to the component with focus.""" 1243 1244 if self.flatReviewContext: 1245 return self.toggleFlatReviewMode(inputEvent) 1246 else: 1247 return braille.returnToRegionWithFocus(inputEvent) 1248 1249 def setContractedBraille(self, inputEvent=None): 1250 """Toggles contracted braille.""" 1251 1252 self._setContractedBraille(inputEvent) 1253 return True 1254 1255 def processRoutingKey(self, inputEvent=None): 1256 """Processes a cursor routing key.""" 1257 1258 braille.processRoutingKey(inputEvent) 1259 return True 1260 1261 def processBrailleCutBegin(self, inputEvent=None): 1262 """Clears the selection and moves the caret offset in the currently 1263 active text area. 1264 """ 1265 1266 obj, caretOffset = self.getBrailleCaretContext(inputEvent) 1267 1268 if caretOffset >= 0: 1269 self.utilities.clearTextSelection(obj) 1270 self.utilities.setCaretOffset(obj, caretOffset) 1271 1272 return True 1273 1274 def processBrailleCutLine(self, inputEvent=None): 1275 """Extends the text selection in the currently active text 1276 area and also copies the selected text to the system clipboard.""" 1277 1278 obj, caretOffset = self.getBrailleCaretContext(inputEvent) 1279 1280 if caretOffset >= 0: 1281 self.utilities.adjustTextSelection(obj, caretOffset) 1282 texti = obj.queryText() 1283 startOffset, endOffset = texti.getSelection(0) 1284 self.utilities.setClipboardText(texti.getText(startOffset, endOffset)) 1285 1286 return True 1287 1288 def routePointerToItem(self, inputEvent=None): 1289 """Moves the mouse pointer to the current item.""" 1290 1291 # Store the original location for scripts which want to restore 1292 # it later. 1293 # 1294 self.oldMouseCoordinates = self.utilities.absoluteMouseCoordinates() 1295 self.lastMouseRoutingTime = time.time() 1296 if self.flatReviewContext: 1297 self.flatReviewContext.routeToCurrent() 1298 return True 1299 1300 if eventsynthesizer.routeToCharacter(orca_state.locusOfFocus): 1301 return True 1302 1303 if eventsynthesizer.routeToObject(orca_state.locusOfFocus): 1304 return True 1305 1306 full = messages.LOCATION_NOT_FOUND_FULL 1307 brief = messages.LOCATION_NOT_FOUND_BRIEF 1308 self.presentMessage(full, brief) 1309 return False 1310 1311 def presentStatusBar(self, inputEvent): 1312 """Speaks and brailles the contents of the status bar and/or default 1313 button of the window with focus. 1314 """ 1315 1316 obj = orca_state.locusOfFocus 1317 self.updateBraille(obj) 1318 1319 frame, dialog = self.utilities.frameAndDialog(obj) 1320 if frame: 1321 start = time.time() 1322 statusbar = self.utilities.statusBar(frame) 1323 end = time.time() 1324 msg = "DEFAULT: Time searching for status bar: %.4f" % (end - start) 1325 debug.println(debug.LEVEL_INFO, msg, True) 1326 if statusbar: 1327 self.pointOfReference['statusBarItems'] = None 1328 self.presentObject(statusbar) 1329 self.pointOfReference['statusBarItems'] = None 1330 else: 1331 full = messages.STATUS_BAR_NOT_FOUND_FULL 1332 brief = messages.STATUS_BAR_NOT_FOUND_BRIEF 1333 self.presentMessage(full, brief) 1334 1335 infobar = self.utilities.infoBar(frame) 1336 if infobar: 1337 speech.speak(self.speechGenerator.generateSpeech(infobar)) 1338 1339 window = dialog or frame 1340 if window: 1341 speech.speak(self.speechGenerator.generateDefaultButton(window)) 1342 1343 def presentTitle(self, inputEvent): 1344 """Speaks and brailles the title of the window with focus.""" 1345 1346 obj = orca_state.locusOfFocus 1347 if self.utilities.isDead(obj): 1348 obj = orca_state.activeWindow 1349 1350 if not obj or self.utilities.isDead(obj): 1351 self.presentMessage(messages.LOCATION_NOT_FOUND_FULL) 1352 return True 1353 1354 title = self.speechGenerator.generateTitle(obj) 1355 for (string, voice) in title: 1356 self.presentMessage(string, voice=voice) 1357 1358 def readCharAttributes(self, inputEvent=None): 1359 """Reads the attributes associated with the current text character. 1360 Calls outCharAttributes to speak a list of attributes. By default, 1361 a certain set of attributes will be spoken. If this is not desired, 1362 then individual application scripts should override this method to 1363 only speak the subset required. 1364 """ 1365 1366 attrs, start, end = self.utilities.textAttributes(orca_state.locusOfFocus, None, True) 1367 1368 # Get a dictionary of text attributes that the user cares about. 1369 [userAttrList, userAttrDict] = self.utilities.stringToKeysAndDict( 1370 _settingsManager.getSetting('enabledSpokenTextAttributes')) 1371 1372 nullValues = ['0', '0mm', 'none', 'false'] 1373 for key in userAttrList: 1374 # Convert the standard key into the non-standard implementor variant. 1375 appKey = self.utilities.getAppNameForAttribute(key) 1376 value = attrs.get(appKey) 1377 ignoreIfValue = userAttrDict.get(key) 1378 if value in nullValues and ignoreIfValue in nullValues: 1379 continue 1380 1381 if value and value != ignoreIfValue: 1382 self.speakMessage(self.utilities.localizeTextAttribute(key, value)) 1383 1384 return True 1385 1386 def leftClickReviewItem(self, inputEvent=None): 1387 """Performs a left mouse button click on the current item.""" 1388 1389 if self.flatReviewContext: 1390 if self.flatReviewContext.clickCurrent(1): 1391 return True 1392 1393 obj = self.flatReviewContext.getCurrentAccessible() 1394 if eventsynthesizer.clickActionOn(obj): 1395 return True 1396 if eventsynthesizer.pressActionOn(obj): 1397 return True 1398 if eventsynthesizer.grabFocusOn(obj): 1399 return True 1400 return False 1401 1402 if self.utilities.queryNonEmptyText(orca_state.locusOfFocus): 1403 if eventsynthesizer.clickCharacter(orca_state.locusOfFocus, 1): 1404 return True 1405 1406 if eventsynthesizer.clickObject(orca_state.locusOfFocus, 1): 1407 return True 1408 1409 full = messages.LOCATION_NOT_FOUND_FULL 1410 brief = messages.LOCATION_NOT_FOUND_BRIEF 1411 self.presentMessage(full, brief) 1412 return False 1413 1414 def rightClickReviewItem(self, inputEvent=None): 1415 """Performs a right mouse button click on the current item.""" 1416 1417 if self.flatReviewContext: 1418 self.flatReviewContext.clickCurrent(3) 1419 return True 1420 1421 if eventsynthesizer.clickCharacter(orca_state.locusOfFocus, 3): 1422 return True 1423 1424 if eventsynthesizer.clickObject(orca_state.locusOfFocus, 3): 1425 return True 1426 1427 full = messages.LOCATION_NOT_FOUND_FULL 1428 brief = messages.LOCATION_NOT_FOUND_BRIEF 1429 self.presentMessage(full, brief) 1430 return False 1431 1432 def spellCurrentItem(self, itemString): 1433 """Spell the current flat review word or line. 1434 1435 Arguments: 1436 - itemString: the string to spell. 1437 """ 1438 1439 for character in itemString: 1440 self.speakCharacter(character) 1441 1442 def _reviewCurrentItem(self, inputEvent, targetCursorCell=0, 1443 speechType=1): 1444 """Presents the current item to the user. 1445 1446 Arguments: 1447 - inputEvent - the current input event. 1448 - targetCursorCell - if non-zero, the target braille cursor cell. 1449 - speechType - the desired presentation: speak (1), spell (2), or 1450 phonetic (3). 1451 """ 1452 1453 context = self.getFlatReviewContext() 1454 [wordString, x, y, width, height] = \ 1455 context.getCurrent(flat_review.Context.WORD) 1456 1457 voice = self.speechGenerator.voice(string=wordString) 1458 1459 # Don't announce anything from speech if the user used 1460 # the Braille display as an input device. 1461 # 1462 if not isinstance(inputEvent, input_event.BrailleEvent): 1463 if (not wordString) \ 1464 or (not len(wordString)) \ 1465 or (wordString == "\n"): 1466 speech.speak(messages.BLANK) 1467 else: 1468 [lineString, x, y, width, height] = \ 1469 context.getCurrent(flat_review.Context.LINE) 1470 if lineString == "\n": 1471 speech.speak(messages.BLANK) 1472 elif wordString.isspace(): 1473 speech.speak(messages.WHITE_SPACE) 1474 elif wordString.isupper() and speechType == 1: 1475 speech.speak(wordString, voice) 1476 elif speechType == 2: 1477 self.spellCurrentItem(wordString) 1478 elif speechType == 3: 1479 self.phoneticSpellCurrentItem(wordString) 1480 elif speechType == 1: 1481 wordString = self.utilities.adjustForRepeats(wordString) 1482 speech.speak(wordString, voice) 1483 1484 self.updateBrailleReview(targetCursorCell) 1485 self.currentReviewContents = wordString 1486 1487 return True 1488 1489 def reviewCurrentAccessible(self, inputEvent): 1490 context = self.getFlatReviewContext() 1491 [zoneString, x, y, width, height] = \ 1492 context.getCurrent(flat_review.Context.ZONE) 1493 1494 # Don't announce anything from speech if the user used 1495 # the Braille display as an input device. 1496 # 1497 if not isinstance(inputEvent, input_event.BrailleEvent): 1498 utterances = self.speechGenerator.generateSpeech( 1499 context.getCurrentAccessible()) 1500 utterances.extend(self.tutorialGenerator.getTutorial( 1501 context.getCurrentAccessible(), False)) 1502 speech.speak(utterances) 1503 return True 1504 1505 def reviewPreviousItem(self, inputEvent): 1506 """Moves the flat review context to the previous item. Places 1507 the flat review cursor at the beginning of the item.""" 1508 1509 context = self.getFlatReviewContext() 1510 1511 moved = context.goPrevious(flat_review.Context.WORD, 1512 flat_review.Context.WRAP_LINE) 1513 1514 if moved: 1515 self._reviewCurrentItem(inputEvent) 1516 self.targetCursorCell = self.getBrailleCursorCell() 1517 1518 return True 1519 1520 def reviewNextItem(self, inputEvent): 1521 """Moves the flat review context to the next item. Places 1522 the flat review cursor at the beginning of the item.""" 1523 1524 context = self.getFlatReviewContext() 1525 1526 moved = context.goNext(flat_review.Context.WORD, 1527 flat_review.Context.WRAP_LINE) 1528 1529 if moved: 1530 self._reviewCurrentItem(inputEvent) 1531 self.targetCursorCell = self.getBrailleCursorCell() 1532 1533 return True 1534 1535 def reviewCurrentCharacter(self, inputEvent): 1536 """Brailles and speaks the current flat review character.""" 1537 1538 self._reviewCurrentCharacter(inputEvent, 1) 1539 1540 return True 1541 1542 def reviewSpellCurrentCharacter(self, inputEvent): 1543 """Brailles and 'spells' (phonetically) the current flat review 1544 character. 1545 """ 1546 1547 self._reviewCurrentCharacter(inputEvent, 2) 1548 1549 return True 1550 1551 def reviewUnicodeCurrentCharacter(self, inputEvent): 1552 """Brailles and speaks unicode information about the current flat 1553 review character. 1554 """ 1555 1556 self._reviewCurrentCharacter(inputEvent, 3) 1557 1558 return True 1559 1560 def _reviewCurrentCharacter(self, inputEvent, speechType=1): 1561 """Presents the current flat review character via braille and speech. 1562 1563 Arguments: 1564 - inputEvent - the current input event. 1565 - speechType - the desired presentation: 1566 speak (1), 1567 phonetic (2) 1568 unicode value information (3) 1569 """ 1570 1571 context = self.getFlatReviewContext() 1572 1573 [charString, x, y, width, height] = \ 1574 context.getCurrent(flat_review.Context.CHAR) 1575 1576 # Don't announce anything from speech if the user used 1577 # the Braille display as an input device. 1578 # 1579 if not isinstance(inputEvent, input_event.BrailleEvent): 1580 if (not charString) or (not len(charString)): 1581 speech.speak(messages.BLANK) 1582 else: 1583 [lineString, x, y, width, height] = \ 1584 context.getCurrent(flat_review.Context.LINE) 1585 if lineString == "\n" and speechType != 3: 1586 speech.speak(messages.BLANK) 1587 elif speechType == 3: 1588 self.speakUnicodeCharacter(charString) 1589 elif speechType == 2: 1590 self.phoneticSpellCurrentItem(charString) 1591 else: 1592 self.speakCharacter(charString) 1593 1594 self.updateBrailleReview() 1595 self.currentReviewContents = charString 1596 1597 return True 1598 1599 def reviewPreviousCharacter(self, inputEvent): 1600 """Moves the flat review context to the previous character. Places 1601 the flat review cursor at character.""" 1602 1603 context = self.getFlatReviewContext() 1604 1605 moved = context.goPrevious(flat_review.Context.CHAR, 1606 flat_review.Context.WRAP_LINE) 1607 1608 if moved: 1609 self._reviewCurrentCharacter(inputEvent) 1610 self.targetCursorCell = self.getBrailleCursorCell() 1611 1612 return True 1613 1614 def reviewEndOfLine(self, inputEvent): 1615 """Moves the flat review context to the end of the line. Places 1616 the flat review cursor at the end of the line.""" 1617 1618 context = self.getFlatReviewContext() 1619 context.goEnd(flat_review.Context.LINE) 1620 1621 self.reviewCurrentCharacter(inputEvent) 1622 self.targetCursorCell = self.getBrailleCursorCell() 1623 1624 return True 1625 1626 def reviewNextCharacter(self, inputEvent): 1627 """Moves the flat review context to the next character. Places 1628 the flat review cursor at character.""" 1629 1630 context = self.getFlatReviewContext() 1631 1632 moved = context.goNext(flat_review.Context.CHAR, 1633 flat_review.Context.WRAP_LINE) 1634 1635 if moved: 1636 self._reviewCurrentCharacter(inputEvent) 1637 self.targetCursorCell = self.getBrailleCursorCell() 1638 1639 return True 1640 1641 def reviewAbove(self, inputEvent): 1642 """Moves the flat review context to the character most directly 1643 above the current flat review cursor. Places the flat review 1644 cursor at character.""" 1645 1646 context = self.getFlatReviewContext() 1647 1648 moved = context.goAbove(flat_review.Context.CHAR, 1649 flat_review.Context.WRAP_LINE) 1650 1651 if moved: 1652 self._reviewCurrentItem(inputEvent, self.targetCursorCell) 1653 1654 return True 1655 1656 def reviewBelow(self, inputEvent): 1657 """Moves the flat review context to the character most directly 1658 below the current flat review cursor. Places the flat review 1659 cursor at character.""" 1660 1661 context = self.getFlatReviewContext() 1662 1663 moved = context.goBelow(flat_review.Context.CHAR, 1664 flat_review.Context.WRAP_LINE) 1665 1666 if moved: 1667 self._reviewCurrentItem(inputEvent, self.targetCursorCell) 1668 1669 return True 1670 1671 def reviewCurrentLine(self, inputEvent): 1672 """Brailles and speaks the current flat review line.""" 1673 1674 self._reviewCurrentLine(inputEvent, 1) 1675 1676 return True 1677 1678 def reviewSpellCurrentLine(self, inputEvent): 1679 """Brailles and spells the current flat review line.""" 1680 1681 self._reviewCurrentLine(inputEvent, 2) 1682 1683 return True 1684 1685 def reviewPhoneticCurrentLine(self, inputEvent): 1686 """Brailles and phonetically spells the current flat review line.""" 1687 1688 self._reviewCurrentLine(inputEvent, 3) 1689 1690 return True 1691 1692 def _reviewCurrentLine(self, inputEvent, speechType=1): 1693 """Presents the current flat review line via braille and speech. 1694 1695 Arguments: 1696 - inputEvent - the current input event. 1697 - speechType - the desired presentation: speak (1), spell (2), or 1698 phonetic (3) 1699 """ 1700 1701 context = self.getFlatReviewContext() 1702 1703 [lineString, x, y, width, height] = \ 1704 context.getCurrent(flat_review.Context.LINE) 1705 1706 voice = self.speechGenerator.voice(string=lineString) 1707 1708 # Don't announce anything from speech if the user used 1709 # the Braille display as an input device. 1710 # 1711 if not isinstance(inputEvent, input_event.BrailleEvent): 1712 if (not lineString) \ 1713 or (not len(lineString)) \ 1714 or (lineString == "\n"): 1715 speech.speak(messages.BLANK) 1716 elif lineString.isspace(): 1717 speech.speak(messages.WHITE_SPACE) 1718 elif lineString.isupper() and (speechType < 2 or speechType > 3): 1719 speech.speak(lineString, voice) 1720 elif speechType == 2: 1721 self.spellCurrentItem(lineString) 1722 elif speechType == 3: 1723 self.phoneticSpellCurrentItem(lineString) 1724 else: 1725 lineString = self.utilities.adjustForRepeats(lineString) 1726 speech.speak(lineString, voice) 1727 1728 self.updateBrailleReview() 1729 self.currentReviewContents = lineString 1730 1731 return True 1732 1733 def reviewPreviousLine(self, inputEvent): 1734 """Moves the flat review context to the beginning of the 1735 previous line.""" 1736 1737 context = self.getFlatReviewContext() 1738 1739 moved = context.goPrevious(flat_review.Context.LINE, 1740 flat_review.Context.WRAP_LINE) 1741 1742 if moved: 1743 self._reviewCurrentLine(inputEvent) 1744 self.targetCursorCell = self.getBrailleCursorCell() 1745 1746 return True 1747 1748 def reviewHome(self, inputEvent): 1749 """Moves the flat review context to the top left of the current 1750 window.""" 1751 1752 context = self.getFlatReviewContext() 1753 1754 context.goBegin() 1755 1756 self._reviewCurrentLine(inputEvent) 1757 self.targetCursorCell = self.getBrailleCursorCell() 1758 1759 return True 1760 1761 def reviewNextLine(self, inputEvent): 1762 """Moves the flat review context to the beginning of the 1763 next line. Places the flat review cursor at the beginning 1764 of the line.""" 1765 1766 context = self.getFlatReviewContext() 1767 1768 moved = context.goNext(flat_review.Context.LINE, 1769 flat_review.Context.WRAP_LINE) 1770 1771 if moved: 1772 self._reviewCurrentLine(inputEvent) 1773 self.targetCursorCell = self.getBrailleCursorCell() 1774 1775 return True 1776 1777 def reviewBottomLeft(self, inputEvent): 1778 """Moves the flat review context to the beginning of the 1779 last line in the window. Places the flat review cursor at 1780 the beginning of the line.""" 1781 1782 context = self.getFlatReviewContext() 1783 1784 context.goEnd(flat_review.Context.WINDOW) 1785 context.goBegin(flat_review.Context.LINE) 1786 self._reviewCurrentLine(inputEvent) 1787 self.targetCursorCell = self.getBrailleCursorCell() 1788 1789 return True 1790 1791 def reviewEnd(self, inputEvent): 1792 """Moves the flat review context to the end of the 1793 last line in the window. Places the flat review cursor 1794 at the end of the line.""" 1795 1796 context = self.getFlatReviewContext() 1797 context.goEnd() 1798 1799 self._reviewCurrentLine(inputEvent) 1800 self.targetCursorCell = self.getBrailleCursorCell() 1801 1802 return True 1803 1804 def reviewCurrentItem(self, inputEvent, targetCursorCell=0): 1805 """Brailles and speaks the current item to the user.""" 1806 1807 self._reviewCurrentItem(inputEvent, targetCursorCell, 1) 1808 1809 return True 1810 1811 def reviewSpellCurrentItem(self, inputEvent, targetCursorCell=0): 1812 """Brailles and spells the current item to the user.""" 1813 1814 self._reviewCurrentItem(inputEvent, targetCursorCell, 2) 1815 1816 return True 1817 1818 def reviewPhoneticCurrentItem(self, inputEvent, targetCursorCell=0): 1819 """Brailles and phonetically spells the current item to the user.""" 1820 1821 self._reviewCurrentItem(inputEvent, targetCursorCell, 3) 1822 1823 return True 1824 1825 def flatReviewCopy(self, inputEvent): 1826 """Copies the contents of the item under flat review to and places 1827 them in the clipboard.""" 1828 1829 if self.flatReviewContext: 1830 self.utilities.setClipboardText(self.currentReviewContents.rstrip("\n")) 1831 self.presentMessage(messages.FLAT_REVIEW_COPIED) 1832 else: 1833 self.presentMessage(messages.FLAT_REVIEW_NOT_IN) 1834 1835 return True 1836 1837 def flatReviewAppend(self, inputEvent): 1838 """Appends the contents of the item under flat review to 1839 the clipboard.""" 1840 1841 if self.flatReviewContext: 1842 self.utilities.appendTextToClipboard(self.currentReviewContents.rstrip("\n")) 1843 self.presentMessage(messages.FLAT_REVIEW_APPENDED) 1844 else: 1845 self.presentMessage(messages.FLAT_REVIEW_NOT_IN) 1846 1847 return True 1848 1849 def flatReviewSayAll(self, inputEvent): 1850 context = self.getFlatReviewContext() 1851 context.goBegin() 1852 1853 while True: 1854 [string, x, y, width, height] = context.getCurrent(flat_review.Context.LINE) 1855 if string is not None: 1856 speech.speak(string) 1857 moved = context.goNext(flat_review.Context.LINE, flat_review.Context.WRAP_LINE) 1858 if not moved: 1859 break 1860 1861 return True 1862 1863 def sayAll(self, inputEvent, obj=None, offset=None): 1864 obj = obj or orca_state.locusOfFocus 1865 if not obj or self.utilities.isDead(obj): 1866 self.presentMessage(messages.LOCATION_NOT_FOUND_FULL) 1867 return True 1868 1869 try: 1870 text = obj.queryText() 1871 except NotImplementedError: 1872 utterances = self.speechGenerator.generateSpeech(obj) 1873 utterances.extend(self.tutorialGenerator.getTutorial(obj, False)) 1874 speech.speak(utterances) 1875 except AttributeError: 1876 pass 1877 else: 1878 if offset is None: 1879 offset = text.caretOffset 1880 speech.sayAll(self.textLines(obj, offset), 1881 self.__sayAllProgressCallback) 1882 1883 return True 1884 1885 def toggleFlatReviewMode(self, inputEvent=None): 1886 """Toggles between flat review mode and focus tracking mode.""" 1887 1888 verbosity = _settingsManager.getSetting('speechVerbosityLevel') 1889 if self.flatReviewContext: 1890 if inputEvent and verbosity != settings.VERBOSITY_LEVEL_BRIEF: 1891 self.presentMessage(messages.FLAT_REVIEW_STOP) 1892 self.flatReviewContext = None 1893 self.updateBraille(orca_state.locusOfFocus) 1894 else: 1895 if inputEvent and verbosity != settings.VERBOSITY_LEVEL_BRIEF: 1896 self.presentMessage(messages.FLAT_REVIEW_START) 1897 context = self.getFlatReviewContext() 1898 [wordString, x, y, width, height] = \ 1899 context.getCurrent(flat_review.Context.WORD) 1900 self._reviewCurrentItem(inputEvent, self.targetCursorCell) 1901 1902 return True 1903 1904 def toggleSilenceSpeech(self, inputEvent=None): 1905 """Toggle the silencing of speech. 1906 1907 Returns True to indicate the input event has been consumed. 1908 """ 1909 1910 self.presentationInterrupt() 1911 if _settingsManager.getSetting('silenceSpeech'): 1912 _settingsManager.setSetting('silenceSpeech', False) 1913 self.presentMessage(messages.SPEECH_ENABLED) 1914 elif not _settingsManager.getSetting('enableSpeech'): 1915 _settingsManager.setSetting('enableSpeech', True) 1916 speech.init() 1917 self.presentMessage(messages.SPEECH_ENABLED) 1918 else: 1919 self.presentMessage(messages.SPEECH_DISABLED) 1920 _settingsManager.setSetting('silenceSpeech', True) 1921 return True 1922 1923 def toggleSpeechVerbosity(self, inputEvent=None): 1924 """Toggles speech verbosity level between verbose and brief.""" 1925 1926 value = _settingsManager.getSetting('speechVerbosityLevel') 1927 if value == settings.VERBOSITY_LEVEL_BRIEF: 1928 self.presentMessage(messages.SPEECH_VERBOSITY_VERBOSE) 1929 _settingsManager.setSetting( 1930 'speechVerbosityLevel', settings.VERBOSITY_LEVEL_VERBOSE) 1931 else: 1932 self.presentMessage(messages.SPEECH_VERBOSITY_BRIEF) 1933 _settingsManager.setSetting( 1934 'speechVerbosityLevel', settings.VERBOSITY_LEVEL_BRIEF) 1935 1936 return True 1937 1938 def toggleSpeakingIndentationJustification(self, inputEvent=None): 1939 """Toggles the speaking of indentation and justification.""" 1940 1941 value = _settingsManager.getSetting('enableSpeechIndentation') 1942 _settingsManager.setSetting('enableSpeechIndentation', not value) 1943 if _settingsManager.getSetting('enableSpeechIndentation'): 1944 full = messages.INDENTATION_JUSTIFICATION_ON_FULL 1945 brief = messages.INDENTATION_JUSTIFICATION_ON_BRIEF 1946 else: 1947 full = messages.INDENTATION_JUSTIFICATION_OFF_FULL 1948 brief = messages.INDENTATION_JUSTIFICATION_OFF_BRIEF 1949 self.presentMessage(full, brief) 1950 1951 return True 1952 1953 def cycleSpeakingPunctuationLevel(self, inputEvent=None): 1954 """ Cycle through the punctuation levels for speech. """ 1955 1956 currentLevel = _settingsManager.getSetting('verbalizePunctuationStyle') 1957 if currentLevel == settings.PUNCTUATION_STYLE_NONE: 1958 newLevel = settings.PUNCTUATION_STYLE_SOME 1959 full = messages.PUNCTUATION_SOME_FULL 1960 brief = messages.PUNCTUATION_SOME_BRIEF 1961 elif currentLevel == settings.PUNCTUATION_STYLE_SOME: 1962 newLevel = settings.PUNCTUATION_STYLE_MOST 1963 full = messages.PUNCTUATION_MOST_FULL 1964 brief = messages.PUNCTUATION_MOST_BRIEF 1965 elif currentLevel == settings.PUNCTUATION_STYLE_MOST: 1966 newLevel = settings.PUNCTUATION_STYLE_ALL 1967 full = messages.PUNCTUATION_ALL_FULL 1968 brief = messages.PUNCTUATION_ALL_BRIEF 1969 else: 1970 newLevel = settings.PUNCTUATION_STYLE_NONE 1971 full = messages.PUNCTUATION_NONE_FULL 1972 brief = messages.PUNCTUATION_NONE_BRIEF 1973 1974 _settingsManager.setSetting('verbalizePunctuationStyle', newLevel) 1975 self.presentMessage(full, brief) 1976 speech.updatePunctuationLevel() 1977 return True 1978 1979 def cycleSettingsProfile(self, inputEvent=None): 1980 """Cycle through the user's existing settings profiles.""" 1981 1982 profiles = _settingsManager.availableProfiles() 1983 if not (profiles and profiles[0]): 1984 self.presentMessage(messages.PROFILE_NOT_FOUND) 1985 return True 1986 1987 isMatch = lambda x: x[1] == _settingsManager.getProfile() 1988 current = list(filter(isMatch, profiles))[0] 1989 try: 1990 name, profileID = profiles[profiles.index(current) + 1] 1991 except IndexError: 1992 name, profileID = profiles[0] 1993 1994 _settingsManager.setProfile(profileID, updateLocale=True) 1995 1996 braille.checkBrailleSetting() 1997 1998 speech.shutdown() 1999 speech.init() 2000 2001 # TODO: This is another "too close to code freeze" hack to cause the 2002 # command names to be presented in the correct language. 2003 self.setupInputEventHandlers() 2004 2005 self.presentMessage(messages.PROFILE_CHANGED % name, name) 2006 return True 2007 2008 def cycleCapitalizationStyle(self, inputEvent=None): 2009 """ Cycle through the speech-dispatcher capitalization styles. """ 2010 2011 currentStyle = _settingsManager.getSetting('capitalizationStyle') 2012 if currentStyle == settings.CAPITALIZATION_STYLE_NONE: 2013 newStyle = settings.CAPITALIZATION_STYLE_SPELL 2014 full = messages.CAPITALIZATION_SPELL_FULL 2015 brief = messages.CAPITALIZATION_SPELL_BRIEF 2016 elif currentStyle == settings.CAPITALIZATION_STYLE_SPELL: 2017 newStyle = settings.CAPITALIZATION_STYLE_ICON 2018 full = messages.CAPITALIZATION_ICON_FULL 2019 brief = messages.CAPITALIZATION_ICON_BRIEF 2020 else: 2021 newStyle = settings.CAPITALIZATION_STYLE_NONE 2022 full = messages.CAPITALIZATION_NONE_FULL 2023 brief = messages.CAPITALIZATION_NONE_BRIEF 2024 2025 _settingsManager.setSetting('capitalizationStyle', newStyle) 2026 self.presentMessage(full, brief) 2027 speech.updateCapitalizationStyle() 2028 return True 2029 2030 def cycleKeyEcho(self, inputEvent=None): 2031 (newKey, newWord, newSentence) = (False, False, False) 2032 key = _settingsManager.getSetting('enableKeyEcho') 2033 word = _settingsManager.getSetting('enableEchoByWord') 2034 sentence = _settingsManager.getSetting('enableEchoBySentence') 2035 2036 if (key, word, sentence) == (False, False, False): 2037 (newKey, newWord, newSentence) = (True, False, False) 2038 full = messages.KEY_ECHO_KEY_FULL 2039 brief = messages.KEY_ECHO_KEY_BRIEF 2040 elif (key, word, sentence) == (True, False, False): 2041 (newKey, newWord, newSentence) = (False, True, False) 2042 full = messages.KEY_ECHO_WORD_FULL 2043 brief = messages.KEY_ECHO_WORD_BRIEF 2044 elif (key, word, sentence) == (False, True, False): 2045 (newKey, newWord, newSentence) = (False, False, True) 2046 full = messages.KEY_ECHO_SENTENCE_FULL 2047 brief = messages.KEY_ECHO_SENTENCE_BRIEF 2048 elif (key, word, sentence) == (False, False, True): 2049 (newKey, newWord, newSentence) = (True, True, False) 2050 full = messages.KEY_ECHO_KEY_AND_WORD_FULL 2051 brief = messages.KEY_ECHO_KEY_AND_WORD_BRIEF 2052 elif (key, word, sentence) == (True, True, False): 2053 (newKey, newWord, newSentence) = (False, True, True) 2054 full = messages.KEY_ECHO_WORD_AND_SENTENCE_FULL 2055 brief = messages.KEY_ECHO_WORD_AND_SENTENCE_BRIEF 2056 else: 2057 (newKey, newWord, newSentence) = (False, False, False) 2058 full = messages.KEY_ECHO_NONE_FULL 2059 brief = messages.KEY_ECHO_NONE_BRIEF 2060 2061 _settingsManager.setSetting('enableKeyEcho', newKey) 2062 _settingsManager.setSetting('enableEchoByWord', newWord) 2063 _settingsManager.setSetting('enableEchoBySentence', newSentence) 2064 self.presentMessage(full, brief) 2065 return True 2066 2067 def changeNumberStyle(self, inputEvent=None): 2068 """Changes spoken number style between digits and words.""" 2069 2070 speakDigits = _settingsManager.getSetting('speakNumbersAsDigits') 2071 if speakDigits: 2072 brief = messages.NUMBER_STYLE_WORDS_BRIEF 2073 full = messages.NUMBER_STYLE_WORDS_FULL 2074 else: 2075 brief = messages.NUMBER_STYLE_DIGITS_BRIEF 2076 full = messages.NUMBER_STYLE_DIGITS_FULL 2077 2078 _settingsManager.setSetting('speakNumbersAsDigits', not speakDigits) 2079 self.presentMessage(full, brief) 2080 return True 2081 2082 def toggleTableCellReadMode(self, inputEvent=None): 2083 """Toggles an indicator for whether we should just read the current 2084 table cell or read the whole row.""" 2085 2086 table = self.utilities.getTable(orca_state.locusOfFocus) 2087 if not table: 2088 self.presentMessage(messages.TABLE_NOT_IN_A) 2089 return True 2090 2091 if not self.utilities.getDocumentForObject(table): 2092 settingName = 'readFullRowInGUITable' 2093 elif self.utilities.isSpreadSheetTable(table): 2094 settingName = 'readFullRowInSpreadSheet' 2095 else: 2096 settingName = 'readFullRowInDocumentTable' 2097 2098 speakRow = _settingsManager.getSetting(settingName) 2099 _settingsManager.setSetting(settingName, not speakRow) 2100 2101 if not speakRow: 2102 line = messages.TABLE_MODE_ROW 2103 else: 2104 line = messages.TABLE_MODE_CELL 2105 2106 self.presentMessage(line) 2107 2108 return True 2109 2110 def doWhereAmI(self, inputEvent, basicOnly): 2111 """Peforms the whereAmI operation. 2112 2113 Arguments: 2114 - inputEvent: The original inputEvent 2115 """ 2116 2117 if self.spellcheck and self.spellcheck.isActive(): 2118 self.spellcheck.presentErrorDetails(not basicOnly) 2119 2120 obj = orca_state.locusOfFocus 2121 if self.utilities.isDead(obj): 2122 obj = orca_state.activeWindow 2123 2124 if not obj or self.utilities.isDead(obj): 2125 self.presentMessage(messages.LOCATION_NOT_FOUND_FULL) 2126 return True 2127 2128 self.updateBraille(obj) 2129 2130 if basicOnly: 2131 formatType = 'basicWhereAmI' 2132 else: 2133 formatType = 'detailedWhereAmI' 2134 speech.speak(self.speechGenerator.generateSpeech( 2135 self.utilities.realActiveAncestor(obj), 2136 alreadyFocused=True, 2137 formatType=formatType, 2138 forceMnemonic=True, 2139 forceList=True, 2140 forceTutorial=True)) 2141 2142 return True 2143 2144 def whereAmIBasic(self, inputEvent): 2145 """Speaks basic information about the current object of interest. 2146 """ 2147 2148 self.doWhereAmI(inputEvent, True) 2149 2150 def whereAmIDetailed(self, inputEvent): 2151 """Speaks detailed/custom information about the current object of 2152 interest. 2153 """ 2154 2155 self.doWhereAmI(inputEvent, False) 2156 2157 def cycleDebugLevel(self, inputEvent=None): 2158 levels = [debug.LEVEL_ALL, "all", 2159 debug.LEVEL_FINEST, "finest", 2160 debug.LEVEL_FINER, "finer", 2161 debug.LEVEL_FINE, "fine", 2162 debug.LEVEL_CONFIGURATION, "configuration", 2163 debug.LEVEL_INFO, "info", 2164 debug.LEVEL_WARNING, "warning", 2165 debug.LEVEL_SEVERE, "severe", 2166 debug.LEVEL_OFF, "off"] 2167 2168 try: 2169 levelIndex = levels.index(debug.debugLevel) + 2 2170 except: 2171 levelIndex = 0 2172 else: 2173 if levelIndex >= len(levels): 2174 levelIndex = 0 2175 2176 debug.debugLevel = levels[levelIndex] 2177 briefMessage = levels[levelIndex + 1] 2178 fullMessage = "Debug level %s." % briefMessage 2179 self.presentMessage(fullMessage, briefMessage) 2180 2181 return True 2182 2183 def whereAmILink(self, inputEvent=None, link=None): 2184 link = link or orca_state.locusOfFocus 2185 if not self.utilities.isLink(link): 2186 self.presentMessage(messages.NOT_ON_A_LINK) 2187 else: 2188 speech.speak(self.speechGenerator.generateLinkInfo(link)) 2189 return True 2190 2191 def _whereAmISelectedText(self, inputEvent, obj): 2192 text, startOffset, endOffset = self.utilities.allSelectedText(obj) 2193 if self.utilities.shouldVerbalizeAllPunctuation(obj): 2194 text = self.utilities.verbalizeAllPunctuation(text) 2195 2196 if not text: 2197 msg = messages.NO_SELECTED_TEXT 2198 else: 2199 msg = messages.SELECTED_TEXT_IS % text 2200 self.speakMessage(msg) 2201 return True 2202 2203 def whereAmISelection(self, inputEvent=None, obj=None): 2204 obj = obj or orca_state.locusOfFocus 2205 if not obj: 2206 return True 2207 2208 container = self.utilities.getSelectionContainer(obj) 2209 if not container: 2210 msg = "INFO: Selection container not found for %s" % obj 2211 debug.println(debug.LEVEL_INFO, msg, True) 2212 return self._whereAmISelectedText(inputEvent, obj) 2213 2214 count = self.utilities.selectedChildCount(container) 2215 childCount = self.utilities.selectableChildCount(container) 2216 self.presentMessage(messages.selectedItemsCount(count, childCount)) 2217 if not count: 2218 return True 2219 2220 utterances = self.speechGenerator.generateSelectedItems(container) 2221 speech.speak(utterances) 2222 return True 2223 2224 ######################################################################## 2225 # # 2226 # AT-SPI OBJECT EVENT HANDLERS # 2227 # # 2228 ######################################################################## 2229 2230 def noOp(self, event): 2231 """Just here to capture events. 2232 2233 Arguments: 2234 - event: the Event 2235 """ 2236 pass 2237 2238 def onActiveChanged(self, event): 2239 """Callback for object:state-changed:active accessibility events.""" 2240 2241 frames = [pyatspi.ROLE_FRAME, 2242 pyatspi.ROLE_DIALOG, 2243 pyatspi.ROLE_FILE_CHOOSER, 2244 pyatspi.ROLE_COLOR_CHOOSER] 2245 2246 if event.source.getRole() in frames: 2247 if event.detail1 and not self.utilities.canBeActiveWindow(event.source): 2248 return 2249 2250 sourceIsActiveWindow = self.utilities.isSameObject( 2251 event.source, orca_state.activeWindow) 2252 2253 if sourceIsActiveWindow and not event.detail1: 2254 if self.utilities.inMenu(): 2255 msg = "DEFAULT: Ignoring event. In menu." 2256 debug.println(debug.LEVEL_INFO, msg, True) 2257 return 2258 2259 if not self.utilities.eventIsUserTriggered(event): 2260 msg = "DEFAULT: Not clearing state. Event is not user triggered." 2261 debug.println(debug.LEVEL_INFO, msg, True) 2262 return 2263 2264 msg = "DEFAULT: Event is for active window. Clearing state." 2265 debug.println(debug.LEVEL_INFO, msg, True) 2266 orca_state.activeWindow = None 2267 return 2268 2269 if not sourceIsActiveWindow and event.detail1: 2270 msg = "DEFAULT: Updating active window to event source." 2271 debug.println(debug.LEVEL_INFO, msg, True) 2272 self.windowActivateTime = time.time() 2273 orca.setLocusOfFocus(event, event.source) 2274 orca_state.activeWindow = event.source 2275 2276 if self.findCommandRun: 2277 self.findCommandRun = False 2278 self.find() 2279 2280 def onActiveDescendantChanged(self, event): 2281 """Callback for object:active-descendant-changed accessibility events.""" 2282 2283 if not event.any_data: 2284 return 2285 2286 if not event.source.getState().contains(pyatspi.STATE_FOCUSED) \ 2287 and not event.any_data.getState().contains(pyatspi.STATE_FOCUSED): 2288 msg = "DEFAULT: Ignoring event. Neither source nor child have focused state." 2289 debug.println(debug.LEVEL_INFO, msg, True) 2290 return 2291 2292 if self.stopSpeechOnActiveDescendantChanged(event): 2293 self.presentationInterrupt() 2294 2295 orca.setLocusOfFocus(event, event.any_data) 2296 2297 def onBusyChanged(self, event): 2298 """Callback for object:state-changed:busy accessibility events.""" 2299 pass 2300 2301 def onCheckedChanged(self, event): 2302 """Callback for object:state-changed:checked accessibility events.""" 2303 2304 obj = event.source 2305 if not self.utilities.isSameObject(obj, orca_state.locusOfFocus): 2306 return 2307 2308 state = obj.getState() 2309 if state.contains(pyatspi.STATE_EXPANDABLE): 2310 return 2311 2312 # Radio buttons normally change their state when you arrow to them, 2313 # so we handle the announcement of their state changes in the focus 2314 # handling code. However, we do need to handle radio buttons where 2315 # the user needs to press the space key to select them. 2316 if obj.getRole() == pyatspi.ROLE_RADIO_BUTTON: 2317 eventString, mods = self.utilities.lastKeyAndModifiers() 2318 if not eventString in [" ", "space"]: 2319 return 2320 2321 oldObj, oldState = self.pointOfReference.get('checkedChange', (None, 0)) 2322 if hash(oldObj) == hash(obj) and oldState == event.detail1: 2323 return 2324 2325 self.updateBraille(obj) 2326 speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True)) 2327 self.pointOfReference['checkedChange'] = hash(obj), event.detail1 2328 2329 def onChildrenAdded(self, event): 2330 """Callback for object:children-changed:add accessibility events.""" 2331 2332 pass 2333 2334 def onChildrenRemoved(self, event): 2335 """Callback for object:children-changed:remove accessibility events.""" 2336 2337 pass 2338 2339 def onCaretMoved(self, event): 2340 """Callback for object:text-caret-moved accessibility events.""" 2341 2342 obj, offset = self.pointOfReference.get("lastCursorPosition", (None, -1)) 2343 if offset == event.detail1 and obj == event.source: 2344 msg = "DEFAULT: Event is for last saved cursor position" 2345 debug.println(debug.LEVEL_INFO, msg, True) 2346 return 2347 2348 state = event.source.getState() 2349 if not state.contains(pyatspi.STATE_SHOWING): 2350 msg = "DEFAULT: Event source is not showing" 2351 debug.println(debug.LEVEL_INFO, msg, True) 2352 if not self.utilities.presentEventFromNonShowingObject(event): 2353 return 2354 2355 if event.source != orca_state.locusOfFocus \ 2356 and state.contains(pyatspi.STATE_FOCUSED): 2357 topLevelObject = self.utilities.topLevelObject(event.source) 2358 if self.utilities.isSameObject(orca_state.activeWindow, topLevelObject): 2359 msg = "DEFAULT: Updating locusOfFocus from %s to %s" % \ 2360 (orca_state.locusOfFocus, event.source) 2361 debug.println(debug.LEVEL_INFO, msg, True) 2362 orca.setLocusOfFocus(event, event.source, False) 2363 else: 2364 msg = "DEFAULT: Source window (%s) is not active window(%s)" \ 2365 % (topLevelObject, orca_state.activeWindow) 2366 debug.println(debug.LEVEL_INFO, msg, True) 2367 2368 if event.source != orca_state.locusOfFocus: 2369 msg = "DEFAULT: Event source (%s) is not locusOfFocus (%s)" \ 2370 % (event.source, orca_state.locusOfFocus) 2371 debug.println(debug.LEVEL_INFO, msg, True) 2372 return 2373 2374 if self.flatReviewContext: 2375 self.toggleFlatReviewMode() 2376 2377 text = event.source.queryText() 2378 try: 2379 caretOffset = text.caretOffset 2380 except: 2381 msg = "DEFAULT: Exception getting caretOffset for %s" % event.source 2382 debug.println(debug.LEVEL_INFO, msg, True) 2383 return 2384 2385 self._saveLastCursorPosition(event.source, text.caretOffset) 2386 if text.getNSelections() > 0: 2387 msg = "DEFAULT: Event source has text selections" 2388 debug.println(debug.LEVEL_INFO, msg, True) 2389 self.utilities.handleTextSelectionChange(event.source) 2390 return 2391 else: 2392 start, end, string = self.utilities.getCachedTextSelection(obj) 2393 if string and self.utilities.handleTextSelectionChange(obj): 2394 msg = "DEFAULT: Event handled as text selection change" 2395 debug.println(debug.LEVEL_INFO, msg, True) 2396 return 2397 2398 msg = "DEFAULT: Presenting text at new caret position" 2399 debug.println(debug.LEVEL_INFO, msg, True) 2400 self._presentTextAtNewCaretPosition(event) 2401 2402 def onDescriptionChanged(self, event): 2403 """Callback for object:property-change:accessible-description events.""" 2404 2405 obj = event.source 2406 descriptions = self.pointOfReference.get('description', {}) 2407 oldDescription = descriptions.get(hash(obj)) 2408 if oldDescription == event.any_data: 2409 msg = "DEFAULT: Old description (%s) is the same as new one" % oldDescription 2410 debug.println(debug.LEVEL_INFO, msg, True) 2411 return 2412 2413 if obj != orca_state.locusOfFocus: 2414 msg = "DEFAULT: Event is for object other than the locusOfFocus" 2415 debug.println(debug.LEVEL_INFO, msg, True) 2416 return 2417 2418 descriptions[hash(obj)] = event.any_data 2419 self.pointOfReference['descriptions'] = descriptions 2420 if event.any_data: 2421 self.presentMessage(event.any_data) 2422 2423 def onDocumentReload(self, event): 2424 """Callback for document:reload accessibility events.""" 2425 2426 pass 2427 2428 def onDocumentLoadComplete(self, event): 2429 """Callback for document:load-complete accessibility events.""" 2430 2431 pass 2432 2433 def onDocumentLoadStopped(self, event): 2434 """Callback for document:load-stopped accessibility events.""" 2435 2436 pass 2437 2438 def onExpandedChanged(self, event): 2439 """Callback for object:state-changed:expanded accessibility events.""" 2440 2441 if not self.utilities.isPresentableExpandedChangedEvent(event): 2442 return 2443 2444 obj = event.source 2445 oldObj, oldState = self.pointOfReference.get('expandedChange', (None, 0)) 2446 if hash(oldObj) == hash(obj) and oldState == event.detail1: 2447 return 2448 2449 self.updateBraille(obj) 2450 speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True)) 2451 self.pointOfReference['expandedChange'] = hash(obj), event.detail1 2452 2453 details = self.utilities.detailsContentForObject(obj) 2454 for detail in details: 2455 self.speakMessage(detail, interrupt=False) 2456 2457 def onIndeterminateChanged(self, event): 2458 """Callback for object:state-changed:indeterminate accessibility events.""" 2459 2460 # If this state is cleared, the new state will become checked or unchecked 2461 # and we should get object:state-changed:checked events for those cases. 2462 # Therefore, if the state is not now indeterminate/partially checked, 2463 # ignore this event. 2464 if not event.detail1: 2465 return 2466 2467 obj = event.source 2468 if not self.utilities.isSameObject(obj, orca_state.locusOfFocus): 2469 return 2470 2471 oldObj, oldState = self.pointOfReference.get('indeterminateChange', (None, 0)) 2472 if hash(oldObj) == hash(obj) and oldState == event.detail1: 2473 return 2474 2475 self.updateBraille(obj) 2476 speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True)) 2477 self.pointOfReference['indeterminateChange'] = hash(obj), event.detail1 2478 2479 def onMouseButton(self, event): 2480 """Callback for mouse:button events.""" 2481 2482 mouseEvent = input_event.MouseButtonEvent(event) 2483 orca_state.lastInputEvent = mouseEvent 2484 if not mouseEvent.pressed: 2485 return 2486 2487 windowChanged = orca_state.activeWindow != mouseEvent.window 2488 if windowChanged: 2489 orca_state.activeWindow = mouseEvent.window 2490 orca.setLocusOfFocus(None, mouseEvent.window, False) 2491 2492 self.presentationInterrupt() 2493 obj = mouseEvent.obj 2494 if obj and obj.getState().contains(pyatspi.STATE_FOCUSED): 2495 orca.setLocusOfFocus(None, obj, windowChanged) 2496 2497 def onNameChanged(self, event): 2498 """Callback for object:property-change:accessible-name events.""" 2499 2500 obj = event.source 2501 names = self.pointOfReference.get('names', {}) 2502 oldName = names.get(hash(obj)) 2503 if oldName == event.any_data: 2504 msg = "DEFAULT: Old name (%s) is the same as new name" % oldName 2505 debug.println(debug.LEVEL_INFO, msg, True) 2506 return 2507 2508 role = obj.getRole() 2509 if role in [pyatspi.ROLE_COMBO_BOX, pyatspi.ROLE_TABLE_CELL]: 2510 msg = "DEFAULT: Event is redundant notification for this role" 2511 debug.println(debug.LEVEL_INFO, msg, True) 2512 return 2513 2514 if role == pyatspi.ROLE_FRAME: 2515 if obj != orca_state.activeWindow: 2516 msg = "DEFAULT: Event is for frame other than the active window" 2517 debug.println(debug.LEVEL_INFO, msg, True) 2518 return 2519 elif obj != orca_state.locusOfFocus: 2520 msg = "DEFAULT: Event is for object other than the locusOfFocus" 2521 debug.println(debug.LEVEL_INFO, msg, True) 2522 return 2523 2524 names[hash(obj)] = event.any_data 2525 self.pointOfReference['names'] = names 2526 self.updateBraille(obj) 2527 speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True)) 2528 2529 def onPressedChanged(self, event): 2530 """Callback for object:state-changed:pressed accessibility events.""" 2531 2532 obj = event.source 2533 if not self.utilities.isSameObject(obj, orca_state.locusOfFocus): 2534 return 2535 2536 oldObj, oldState = self.pointOfReference.get('pressedChange', (None, 0)) 2537 if hash(oldObj) == hash(obj) and oldState == event.detail1: 2538 return 2539 2540 self.updateBraille(obj) 2541 speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True)) 2542 self.pointOfReference['pressedChange'] = hash(obj), event.detail1 2543 2544 def onSelectedChanged(self, event): 2545 """Callback for object:state-changed:selected accessibility events.""" 2546 2547 obj = event.source 2548 obj.clearCache() 2549 state = obj.getState() 2550 if not state.contains(pyatspi.STATE_FOCUSED): 2551 return 2552 2553 if not self.utilities.isSameObject(orca_state.locusOfFocus, obj): 2554 return 2555 2556 if _settingsManager.getSetting('onlySpeakDisplayedText'): 2557 return 2558 2559 isSelected = state.contains(pyatspi.STATE_SELECTED) 2560 if isSelected != event.detail1: 2561 msg = "DEFAULT: Bogus event: detail1 doesn't match state" 2562 debug.println(debug.LEVEL_INFO, msg, True) 2563 return 2564 2565 oldObj, oldState = self.pointOfReference.get('selectedChange', (None, 0)) 2566 if hash(oldObj) == hash(obj) and oldState == event.detail1: 2567 msg = "DEFAULT: Duplicate or spam event" 2568 debug.println(debug.LEVEL_INFO, msg, True) 2569 return 2570 2571 announceState = False 2572 keyString, mods = self.utilities.lastKeyAndModifiers() 2573 if keyString == "space": 2574 announceState = True 2575 elif keyString in ["Down", "Up"] \ 2576 and isSelected and obj.getRole() == pyatspi.ROLE_TABLE_CELL: 2577 announceState = True 2578 2579 if not announceState: 2580 return 2581 2582 # TODO - JD: Unlike the other state-changed callbacks, it seems unwise 2583 # to call generateSpeech() here because that also will present the 2584 # expandable state if appropriate for the object type. The generators 2585 # need to gain some smarts w.r.t. state changes. 2586 2587 if event.detail1: 2588 self.speakMessage(messages.TEXT_SELECTED, interrupt=False) 2589 else: 2590 self.speakMessage(messages.TEXT_UNSELECTED, interrupt=False) 2591 2592 self.pointOfReference['selectedChange'] = hash(obj), event.detail1 2593 2594 def onSelectionChanged(self, event): 2595 """Callback for object:selection-changed accessibility events.""" 2596 2597 obj = event.source 2598 state = obj.getState() 2599 2600 if self.utilities.handlePasteLocusOfFocusChange(): 2601 if self.utilities.topLevelObjectIsActiveAndCurrent(event.source): 2602 orca.setLocusOfFocus(event, event.source, False) 2603 elif self.utilities.handleContainerSelectionChange(event.source): 2604 return 2605 else: 2606 if state.contains(pyatspi.STATE_MANAGES_DESCENDANTS): 2607 return 2608 2609 # TODO - JD: We need to give more thought to where we look to this 2610 # event and where we prefer object:state-changed:selected. 2611 2612 # If the current item's selection is toggled, we'll present that 2613 # via the state-changed event. 2614 keyString, mods = self.utilities.lastKeyAndModifiers() 2615 if keyString == "space": 2616 return 2617 2618 role = obj.getRole() 2619 if role == pyatspi.ROLE_COMBO_BOX and not state.contains(pyatspi.STATE_EXPANDED): 2620 entry = self.utilities.getEntryForEditableComboBox(event.source) 2621 if entry and entry.getState().contains(pyatspi.STATE_FOCUSED): 2622 return 2623 2624 # If a wizard-like notebook page being reviewed changes, we might not get 2625 # any events to update the locusOfFocus. As a result, subsequent flat 2626 # review commands will continue to present the stale content. 2627 if role == pyatspi.ROLE_PAGE_TAB_LIST and self.flatReviewContext: 2628 self.flatReviewContext = None 2629 2630 mouseReviewItem = mouse_review.reviewer.getCurrentItem() 2631 selectedChildren = self.utilities.selectedChildren(obj) 2632 for child in selectedChildren: 2633 if pyatspi.findAncestor(orca_state.locusOfFocus, lambda x: x == child): 2634 msg = "DEFAULT: Child %s is ancestor of locusOfFocus" % child 2635 debug.println(debug.LEVEL_INFO, msg, True) 2636 self._saveFocusedObjectInfo(orca_state.locusOfFocus) 2637 return 2638 2639 if child == mouseReviewItem: 2640 msg = "DEFAULT: Child %s is current mouse review item" % child 2641 debug.println(debug.LEVEL_INFO, msg, True) 2642 continue 2643 2644 if child.getRole() == pyatspi.ROLE_PAGE_TAB and orca_state.locusOfFocus \ 2645 and child.name == orca_state.locusOfFocus.name \ 2646 and not state.contains(pyatspi.STATE_FOCUSED): 2647 msg = "DEFAULT: %s's selection redundant to %s" % (child, orca_state.locusOfFocus) 2648 debug.println(debug.LEVEL_INFO, msg, True) 2649 break 2650 2651 if not self.utilities.isLayoutOnly(child): 2652 orca.setLocusOfFocus(event, child) 2653 break 2654 2655 def onSensitiveChanged(self, event): 2656 """Callback for object:state-changed:sensitive accessibility events.""" 2657 pass 2658 2659 def onFocus(self, event): 2660 """Callback for focus: accessibility events.""" 2661 2662 pass 2663 2664 def onFocusedChanged(self, event): 2665 """Callback for object:state-changed:focused accessibility events.""" 2666 2667 if not event.detail1: 2668 return 2669 2670 obj = event.source 2671 state = obj.getState() 2672 if not state.contains(pyatspi.STATE_FOCUSED): 2673 return 2674 2675 window, dialog = self.utilities.frameAndDialog(obj) 2676 clearCache = window != orca_state.activeWindow 2677 if window and not self.utilities.canBeActiveWindow(window, clearCache) and not dialog: 2678 return 2679 2680 try: 2681 childCount = obj.childCount 2682 role = obj.getRole() 2683 except: 2684 msg = "DEFAULT: Exception getting childCount and role for %s" % obj 2685 debug.println(debug.LEVEL_INFO, msg, True) 2686 return 2687 2688 if childCount and role != pyatspi.ROLE_COMBO_BOX: 2689 selectedChildren = self.utilities.selectedChildren(obj) 2690 if selectedChildren: 2691 obj = selectedChildren[0] 2692 2693 orca.setLocusOfFocus(event, obj) 2694 2695 def onShowingChanged(self, event): 2696 """Callback for object:state-changed:showing accessibility events.""" 2697 2698 obj = event.source 2699 role = obj.getRole() 2700 if role == pyatspi.ROLE_NOTIFICATION: 2701 speech.speak(self.speechGenerator.generateSpeech(obj)) 2702 visibleOnly = not self.utilities.isStatusBarNotification(obj) 2703 labels = self.utilities.unrelatedLabels(obj, visibleOnly, 1) 2704 msg = ''.join(map(self.utilities.displayedText, labels)) 2705 self.displayBrailleMessage(msg, flashTime=settings.brailleFlashTime) 2706 notification_messages.saveMessage(msg) 2707 return 2708 2709 if role == pyatspi.ROLE_TOOL_TIP: 2710 keyString, mods = self.utilities.lastKeyAndModifiers() 2711 if keyString != "F1" \ 2712 and not _settingsManager.getSetting('presentToolTips'): 2713 return 2714 if event.detail1: 2715 self.presentObject(obj) 2716 return 2717 2718 if orca_state.locusOfFocus and keyString == "F1": 2719 obj = orca_state.locusOfFocus 2720 self.updateBraille(obj) 2721 speech.speak(self.speechGenerator.generateSpeech(obj, priorObj=event.source)) 2722 return 2723 2724 def onTextAttributesChanged(self, event): 2725 """Callback for object:text-attributes-changed accessibility events.""" 2726 2727 if not self.utilities.isPresentableTextChangedEventForLocusOfFocus(event): 2728 return 2729 2730 text = self.utilities.queryNonEmptyText(event.source) 2731 if not text: 2732 msg = "DEFAULT: Querying non-empty text returned None" 2733 debug.println(debug.LEVEL_INFO, msg, True) 2734 return 2735 2736 if _settingsManager.getSetting('speakMisspelledIndicator'): 2737 offset = text.caretOffset 2738 if not text.getText(offset, offset+1).isalnum(): 2739 offset -= 1 2740 if self.utilities.isWordMisspelled(event.source, offset-1) \ 2741 or self.utilities.isWordMisspelled(event.source, offset+1): 2742 self.speakMessage(messages.MISSPELLED) 2743 2744 def onTextDeleted(self, event): 2745 """Callback for object:text-changed:delete accessibility events.""" 2746 2747 if not self.utilities.isPresentableTextChangedEventForLocusOfFocus(event): 2748 return 2749 2750 self.utilities.handleUndoTextEvent(event) 2751 2752 orca.setLocusOfFocus(event, event.source, False) 2753 self.updateBraille(event.source) 2754 2755 full, brief = "", "" 2756 if self.utilities.isClipboardTextChangedEvent(event): 2757 msg = "DEFAULT: Deletion is believed to be due to clipboard cut" 2758 debug.println(debug.LEVEL_INFO, msg, True) 2759 full, brief = messages.CLIPBOARD_CUT_FULL, messages.CLIPBOARD_CUT_BRIEF 2760 elif self.utilities.isSelectedTextDeletionEvent(event): 2761 msg = "DEFAULT: Deletion is believed to be due to deleting selected text" 2762 debug.println(debug.LEVEL_INFO, msg, True) 2763 full = messages.SELECTION_DELETED 2764 2765 if full or brief: 2766 self.presentMessage(full, brief) 2767 self.utilities.updateCachedTextSelection(event.source) 2768 return 2769 2770 string = self.utilities.deletedText(event) 2771 if self.utilities.isDeleteCommandTextDeletionEvent(event): 2772 msg = "DEFAULT: Deletion is believed to be due to Delete command" 2773 debug.println(debug.LEVEL_INFO, msg, True) 2774 string = self.utilities.getCharacterAtOffset(event.source) 2775 elif self.utilities.isBackSpaceCommandTextDeletionEvent(event): 2776 msg = "DEFAULT: Deletion is believed to be due to BackSpace command" 2777 debug.println(debug.LEVEL_INFO, msg, True) 2778 else: 2779 msg = "INFO: Event is not being presented due to lack of cause" 2780 debug.println(debug.LEVEL_INFO, msg, True) 2781 return 2782 2783 if len(string) == 1: 2784 self.speakCharacter(string) 2785 else: 2786 voice = self.speechGenerator.voice(string=string) 2787 string = self.utilities.adjustForRepeats(string) 2788 speech.speak(string, voice) 2789 2790 def onTextInserted(self, event): 2791 """Callback for object:text-changed:insert accessibility events.""" 2792 2793 if not self.utilities.isPresentableTextChangedEventForLocusOfFocus(event): 2794 return 2795 2796 self.utilities.handleUndoTextEvent(event) 2797 2798 if event.source == orca_state.locusOfFocus and self.utilities.isAutoTextEvent(event): 2799 self._saveFocusedObjectInfo(event.source) 2800 orca.setLocusOfFocus(event, event.source, False) 2801 self.updateBraille(event.source) 2802 2803 full, brief = "", "" 2804 if self.utilities.isClipboardTextChangedEvent(event): 2805 msg = "DEFAULT: Insertion is believed to be due to clipboard paste" 2806 debug.println(debug.LEVEL_INFO, msg, True) 2807 full, brief = messages.CLIPBOARD_PASTED_FULL, messages.CLIPBOARD_PASTED_BRIEF 2808 elif self.utilities.isSelectedTextRestoredEvent(event): 2809 msg = "DEFAULT: Insertion is believed to be due to restoring selected text" 2810 debug.println(debug.LEVEL_INFO, msg, True) 2811 full = messages.SELECTION_RESTORED 2812 2813 if full or brief: 2814 self.presentMessage(full, brief) 2815 self.utilities.updateCachedTextSelection(event.source) 2816 return 2817 2818 speakString = True 2819 2820 # Because some implementations are broken. 2821 string = self.utilities.insertedText(event) 2822 2823 if self.utilities.lastInputEventWasPageSwitch(): 2824 msg = "DEFAULT: Insertion is believed to be due to page switch" 2825 debug.println(debug.LEVEL_INFO, msg, True) 2826 speakString = False 2827 elif self.utilities.lastInputEventWasCommand(): 2828 msg = "DEFAULT: Insertion is believed to be due to command" 2829 debug.println(debug.LEVEL_INFO, msg, True) 2830 elif self.utilities.isMiddleMouseButtonTextInsertionEvent(event): 2831 msg = "DEFAULT: Insertion is believed to be due to middle mouse button" 2832 debug.println(debug.LEVEL_INFO, msg, True) 2833 elif self.utilities.isEchoableTextInsertionEvent(event): 2834 msg = "DEFAULT: Insertion is believed to be echoable" 2835 debug.println(debug.LEVEL_INFO, msg, True) 2836 elif self.utilities.isAutoTextEvent(event): 2837 msg = "DEFAULT: Insertion is believed to be auto text event" 2838 debug.println(debug.LEVEL_INFO, msg, True) 2839 elif self.utilities.isSelectedTextInsertionEvent(event): 2840 msg = "DEFAULT: Insertion is also selected" 2841 debug.println(debug.LEVEL_INFO, msg, True) 2842 else: 2843 msg = "DEFAULT: Not speaking inserted string due to lack of cause" 2844 debug.println(debug.LEVEL_INFO, msg, True) 2845 speakString = False 2846 2847 if speakString: 2848 if len(string) == 1: 2849 self.speakCharacter(string) 2850 else: 2851 voice = self.speechGenerator.voice(string=string) 2852 string = self.utilities.adjustForRepeats(string) 2853 speech.speak(string, voice) 2854 2855 if len(string) != 1: 2856 return 2857 2858 if _settingsManager.getSetting('enableEchoBySentence') \ 2859 and self.echoPreviousSentence(event.source): 2860 return 2861 2862 if _settingsManager.getSetting('enableEchoByWord'): 2863 self.echoPreviousWord(event.source) 2864 2865 def onTextSelectionChanged(self, event): 2866 """Callback for object:text-selection-changed accessibility events.""" 2867 2868 obj = event.source 2869 2870 # We won't handle undo here as it can lead to double-presentation. 2871 # If there is an application for which text-changed events are 2872 # missing upon undo, handle them in an app or toolkit script. 2873 2874 self.utilities.handleTextSelectionChange(obj) 2875 self.updateBraille(obj) 2876 2877 def onColumnReordered(self, event): 2878 """Callback for object:column-reordered accessibility events.""" 2879 2880 if not self.utilities.lastInputEventWasTableSort(): 2881 return 2882 2883 if event.source != self.utilities.getTable(orca_state.locusOfFocus): 2884 return 2885 2886 self.pointOfReference['last-table-sort-time'] = time.time() 2887 self.presentMessage(messages.TABLE_REORDERED_COLUMNS) 2888 2889 def onRowReordered(self, event): 2890 """Callback for object:row-reordered accessibility events.""" 2891 2892 if not self.utilities.lastInputEventWasTableSort(): 2893 return 2894 2895 if event.source != self.utilities.getTable(orca_state.locusOfFocus): 2896 return 2897 2898 self.pointOfReference['last-table-sort-time'] = time.time() 2899 self.presentMessage(messages.TABLE_REORDERED_ROWS) 2900 2901 def onValueChanged(self, event): 2902 """Called whenever an object's value changes. Currently, the 2903 value changes for non-focused objects are ignored. 2904 2905 Arguments: 2906 - event: the Event 2907 """ 2908 2909 obj = event.source 2910 role = obj.getRole() 2911 2912 try: 2913 value = obj.queryValue() 2914 currentValue = value.currentValue 2915 except NotImplementedError: 2916 msg = "ERROR: %s doesn't implement AtspiValue" % obj 2917 debug.println(debug.LEVEL_INFO, msg, True) 2918 return 2919 except: 2920 msg = "ERROR: Exception getting current value for %s" % obj 2921 debug.println(debug.LEVEL_INFO, msg, True) 2922 return 2923 2924 if "oldValue" in self.pointOfReference \ 2925 and (currentValue == self.pointOfReference["oldValue"]): 2926 return 2927 2928 isProgressBarUpdate, msg = self.utilities.isProgressBarUpdate(obj, event) 2929 msg = "DEFAULT: Is progress bar update: %s, %s" % (isProgressBarUpdate, msg) 2930 debug.println(debug.LEVEL_INFO, msg, True) 2931 2932 if not isProgressBarUpdate and obj != orca_state.locusOfFocus: 2933 msg = "DEFAULT: Source != locusOfFocus (%s)" % orca_state.locusOfFocus 2934 debug.println(debug.LEVEL_INFO, msg, True) 2935 return 2936 2937 if role == pyatspi.ROLE_SPIN_BUTTON: 2938 self._saveFocusedObjectInfo(event.source) 2939 2940 self.pointOfReference["oldValue"] = currentValue 2941 self.updateBraille(obj, isProgressBarUpdate=isProgressBarUpdate) 2942 speech.speak(self.speechGenerator.generateSpeech( 2943 obj, alreadyFocused=True, isProgressBarUpdate=isProgressBarUpdate)) 2944 self.__play(self.soundGenerator.generateSound( 2945 obj, alreadyFocused=True, isProgressBarUpdate=isProgressBarUpdate)) 2946 2947 def onWindowActivated(self, event): 2948 """Called whenever a toplevel window is activated. 2949 2950 Arguments: 2951 - event: the Event 2952 """ 2953 2954 if not self.utilities.canBeActiveWindow(event.source, False): 2955 return 2956 2957 if self.utilities.isSameObject(event.source, orca_state.activeWindow): 2958 msg = "DEFAULT: Event is for active window." 2959 debug.println(debug.LEVEL_INFO, msg, True) 2960 return 2961 2962 self.pointOfReference = {} 2963 2964 self.windowActivateTime = time.time() 2965 orca_state.activeWindow = event.source 2966 2967 if self.utilities.isKeyGrabEvent(event): 2968 msg = "DEFAULT: Ignoring event. Likely from key grab." 2969 debug.println(debug.LEVEL_INFO, msg, True) 2970 return 2971 2972 try: 2973 childCount = event.source.childCount 2974 childRole = event.source[0].getRole() 2975 except: 2976 pass 2977 else: 2978 if childCount == 1 and childRole == pyatspi.ROLE_MENU: 2979 orca.setLocusOfFocus(event, event.source[0]) 2980 return 2981 2982 orca.setLocusOfFocus(event, event.source) 2983 2984 def onWindowCreated(self, event): 2985 """Callback for window:create accessibility events.""" 2986 2987 pass 2988 2989 def onWindowDestroyed(self, event): 2990 """Callback for window:destroy accessibility events.""" 2991 2992 pass 2993 2994 def onWindowDeactivated(self, event): 2995 """Called whenever a toplevel window is deactivated. 2996 2997 Arguments: 2998 - event: the Event 2999 """ 3000 3001 if self.utilities.inMenu(): 3002 msg = "DEFAULT: Ignoring event. In menu." 3003 debug.println(debug.LEVEL_INFO, msg, True) 3004 return 3005 3006 if event.source != orca_state.activeWindow: 3007 msg = "DEFAULT: Ignoring event. Not for active window %s." % orca_state.activeWindow 3008 debug.println(debug.LEVEL_INFO, msg, True) 3009 return 3010 3011 if self.utilities.isKeyGrabEvent(event): 3012 msg = "DEFAULT: Ignoring event. Likely from key grab." 3013 debug.println(debug.LEVEL_INFO, msg, True) 3014 return 3015 3016 self.presentationInterrupt() 3017 self.clearBraille() 3018 3019 if self.flatReviewContext: 3020 self.flatReviewContext = None 3021 3022 self.pointOfReference = {} 3023 3024 if not self.utilities.eventIsUserTriggered(event): 3025 msg = "DEFAULT: Not clearing state. Event is not user triggered." 3026 debug.println(debug.LEVEL_INFO, msg, True) 3027 return 3028 3029 msg = "DEFAULT: Clearing state." 3030 debug.println(debug.LEVEL_INFO, msg, True) 3031 3032 orca.setLocusOfFocus(event, None) 3033 orca_state.activeWindow = None 3034 orca_state.activeScript = None 3035 orca_state.listNotificationsModeEnabled = False 3036 orca_state.learnModeEnabled = False 3037 3038 def onClipboardContentsChanged(self, *args): 3039 if self.flatReviewContext: 3040 return 3041 3042 if not self.utilities.objectContentsAreInClipboard(): 3043 return 3044 3045 if not self.utilities.topLevelObjectIsActiveAndCurrent(): 3046 return 3047 3048 if self.utilities.lastInputEventWasCopy(): 3049 self.presentMessage(messages.CLIPBOARD_COPIED_FULL, messages.CLIPBOARD_COPIED_BRIEF) 3050 return 3051 3052 if not self.utilities.lastInputEventWasCut(): 3053 return 3054 3055 try: 3056 state = orca_state.locusOfFocus.getState() 3057 except: 3058 msg = "ERROR: Exception getting state of %s" % orca_state.locusOfFocus 3059 debug.println(debug.LEVEL_INFO, msg, True) 3060 else: 3061 if state.contains(pyatspi.STATE_EDITABLE): 3062 return 3063 3064 self.presentMessage(messages.CLIPBOARD_CUT_FULL, messages.CLIPBOARD_CUT_BRIEF) 3065 3066 ######################################################################## 3067 # # 3068 # Methods for presenting content # 3069 # # 3070 ######################################################################## 3071 3072 def _presentTextAtNewCaretPosition(self, event, otherObj=None): 3073 obj = otherObj or event.source 3074 self.updateBrailleForNewCaretPosition(obj) 3075 if self._inSayAll: 3076 return 3077 3078 if self.utilities.lastInputEventWasLineNav(): 3079 msg = "DEFAULT: Presenting result of line nav" 3080 debug.println(debug.LEVEL_INFO, msg, True) 3081 self.sayLine(obj) 3082 return 3083 3084 if self.utilities.lastInputEventWasWordNav(): 3085 msg = "DEFAULT: Presenting result of word nav" 3086 debug.println(debug.LEVEL_INFO, msg, True) 3087 self.sayWord(obj) 3088 return 3089 3090 if self.utilities.lastInputEventWasCharNav(): 3091 msg = "DEFAULT: Presenting result of char nav" 3092 debug.println(debug.LEVEL_INFO, msg, True) 3093 self.sayCharacter(obj) 3094 return 3095 3096 if self.utilities.lastInputEventWasPageNav(): 3097 msg = "DEFAULT: Presenting result of page nav" 3098 debug.println(debug.LEVEL_INFO, msg, True) 3099 self.sayLine(obj) 3100 return 3101 3102 if self.utilities.lastInputEventWasLineBoundaryNav(): 3103 msg = "DEFAULT: Presenting result of line boundary nav" 3104 debug.println(debug.LEVEL_INFO, msg, True) 3105 self.sayCharacter(obj) 3106 return 3107 3108 if self.utilities.lastInputEventWasFileBoundaryNav(): 3109 msg = "DEFAULT: Presenting result of file boundary nav" 3110 debug.println(debug.LEVEL_INFO, msg, True) 3111 self.sayLine(obj) 3112 return 3113 3114 if self.utilities.lastInputEventWasPrimaryMouseRelease(): 3115 start, end, string = self.utilities.getCachedTextSelection(event.source) 3116 if not string: 3117 msg = "DEFAULT: Presenting result of primary mouse button release" 3118 debug.println(debug.LEVEL_INFO, msg, True) 3119 self.sayLine(obj) 3120 return 3121 3122 def _rewindSayAll(self, context, minCharCount=10): 3123 if not _settingsManager.getSetting('rewindAndFastForwardInSayAll'): 3124 return False 3125 3126 index = self._sayAllContexts.index(context) 3127 self._sayAllContexts = self._sayAllContexts[0:index] 3128 while self._sayAllContexts: 3129 context = self._sayAllContexts.pop() 3130 if context.endOffset - context.startOffset > minCharCount: 3131 break 3132 3133 try: 3134 text = context.obj.queryText() 3135 except: 3136 pass 3137 else: 3138 orca.setLocusOfFocus(None, context.obj, notifyScript=False) 3139 text.setCaretOffset(context.startOffset) 3140 3141 self.sayAll(None, context.obj, context.startOffset) 3142 return True 3143 3144 def _fastForwardSayAll(self, context): 3145 if not _settingsManager.getSetting('rewindAndFastForwardInSayAll'): 3146 return False 3147 3148 try: 3149 text = context.obj.queryText() 3150 except: 3151 pass 3152 else: 3153 orca.setLocusOfFocus(None, context.obj, notifyScript=False) 3154 text.setCaretOffset(context.endOffset) 3155 3156 self.sayAll(None, context.obj, context.endOffset) 3157 return True 3158 3159 def __sayAllProgressCallback(self, context, progressType): 3160 # [[[TODO: WDW - this needs work. Need to be able to manage 3161 # the monitoring of progress and couple that with both updating 3162 # the visual progress of what is being spoken as well as 3163 # positioning the cursor when speech has stopped.]]] 3164 # 3165 try: 3166 text = context.obj.queryText() 3167 char = text.getText(context.currentOffset, context.currentOffset+1) 3168 except: 3169 return 3170 3171 # Setting the caret at the offset of an embedded object results in 3172 # focus changes. 3173 if char == self.EMBEDDED_OBJECT_CHARACTER: 3174 return 3175 3176 if progressType == speechserver.SayAllContext.PROGRESS: 3177 orca.emitRegionChanged( 3178 context.obj, context.currentOffset, context.currentEndOffset, orca.SAY_ALL) 3179 return 3180 3181 if progressType == speechserver.SayAllContext.INTERRUPTED: 3182 if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent): 3183 self._sayAllIsInterrupted = True 3184 lastKey = orca_state.lastInputEvent.event_string 3185 if lastKey == "Down" and self._fastForwardSayAll(context): 3186 return 3187 elif lastKey == "Up" and self._rewindSayAll(context): 3188 return 3189 3190 self._inSayAll = False 3191 self._sayAllContexts = [] 3192 orca.emitRegionChanged(context.obj, context.currentOffset) 3193 text.setCaretOffset(context.currentOffset) 3194 elif progressType == speechserver.SayAllContext.COMPLETED: 3195 orca.setLocusOfFocus(None, context.obj, notifyScript=False) 3196 orca.emitRegionChanged(context.obj, context.currentOffset, mode=orca.SAY_ALL) 3197 text.setCaretOffset(context.currentOffset) 3198 3199 # If there is a selection, clear it. See bug #489504 for more details. 3200 # 3201 if text.getNSelections() > 0: 3202 text.setSelection(0, context.currentOffset, context.currentOffset) 3203 3204 def inSayAll(self, treatInterruptedAsIn=True): 3205 if self._inSayAll: 3206 msg = "DEFAULT: In SayAll" 3207 debug.println(debug.LEVEL_INFO, msg, True) 3208 return True 3209 3210 if self._sayAllIsInterrupted: 3211 msg = "DEFAULT: SayAll is interrupted" 3212 debug.println(debug.LEVEL_INFO, msg, True) 3213 return treatInterruptedAsIn 3214 3215 msg = "DEFAULT: Not in SayAll" 3216 debug.println(debug.LEVEL_INFO, msg, True) 3217 return False 3218 3219 def echoPreviousSentence(self, obj): 3220 """Speaks the sentence prior to the caret, as long as there is 3221 a sentence prior to the caret and there is no intervening sentence 3222 delimiter between the caret and the end of the sentence. 3223 3224 The entry condition for this method is that the character 3225 prior to the current caret position is a sentence delimiter, 3226 and it's what caused this method to be called in the first 3227 place. 3228 3229 Arguments: 3230 - obj: an Accessible object that implements the AccessibleText 3231 interface. 3232 """ 3233 3234 try: 3235 text = obj.queryText() 3236 except NotImplementedError: 3237 return False 3238 3239 offset = text.caretOffset - 1 3240 previousOffset = text.caretOffset - 2 3241 if (offset < 0 or previousOffset < 0): 3242 return False 3243 3244 [currentChar, startOffset, endOffset] = \ 3245 text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR) 3246 [previousChar, startOffset, endOffset] = \ 3247 text.getTextAtOffset(previousOffset, pyatspi.TEXT_BOUNDARY_CHAR) 3248 if not self.utilities.isSentenceDelimiter(currentChar, previousChar): 3249 return False 3250 3251 # OK - we seem to be cool so far. So...starting with what 3252 # should be the last character in the sentence (caretOffset - 2), 3253 # work our way to the beginning of the sentence, stopping when 3254 # we hit another sentence delimiter. 3255 # 3256 sentenceEndOffset = text.caretOffset - 2 3257 sentenceStartOffset = sentenceEndOffset 3258 3259 while sentenceStartOffset >= 0: 3260 [currentChar, startOffset, endOffset] = \ 3261 text.getTextAtOffset(sentenceStartOffset, 3262 pyatspi.TEXT_BOUNDARY_CHAR) 3263 [previousChar, startOffset, endOffset] = \ 3264 text.getTextAtOffset(sentenceStartOffset-1, 3265 pyatspi.TEXT_BOUNDARY_CHAR) 3266 if self.utilities.isSentenceDelimiter(currentChar, previousChar): 3267 break 3268 else: 3269 sentenceStartOffset -= 1 3270 3271 # If we came across a sentence delimiter before hitting any 3272 # text, we really don't have a previous sentence. 3273 # 3274 # Otherwise, get the sentence. Remember we stopped when we 3275 # hit a sentence delimiter, so the sentence really starts at 3276 # sentenceStartOffset + 1. getText also does not include 3277 # the character at sentenceEndOffset, so we need to adjust 3278 # for that, too. 3279 # 3280 if sentenceStartOffset == sentenceEndOffset: 3281 return False 3282 else: 3283 sentence = self.utilities.substring(obj, sentenceStartOffset + 1, 3284 sentenceEndOffset + 1) 3285 3286 voice = self.speechGenerator.voice(string=sentence) 3287 sentence = self.utilities.adjustForRepeats(sentence) 3288 speech.speak(sentence, voice) 3289 return True 3290 3291 def echoPreviousWord(self, obj, offset=None): 3292 """Speaks the word prior to the caret, as long as there is 3293 a word prior to the caret and there is no intervening word 3294 delimiter between the caret and the end of the word. 3295 3296 The entry condition for this method is that the character 3297 prior to the current caret position is a word delimiter, 3298 and it's what caused this method to be called in the first 3299 place. 3300 3301 Arguments: 3302 - obj: an Accessible object that implements the AccessibleText 3303 interface. 3304 - offset: if not None, the offset within the text to use as the 3305 end of the word. 3306 """ 3307 3308 try: 3309 text = obj.queryText() 3310 except NotImplementedError: 3311 return False 3312 3313 if not offset: 3314 if text.caretOffset == -1: 3315 offset = text.characterCount 3316 else: 3317 offset = text.caretOffset - 1 3318 3319 if (offset < 0): 3320 return False 3321 3322 [char, startOffset, endOffset] = \ 3323 text.getTextAtOffset( \ 3324 offset, 3325 pyatspi.TEXT_BOUNDARY_CHAR) 3326 if not self.utilities.isWordDelimiter(char): 3327 return False 3328 3329 # OK - we seem to be cool so far. So...starting with what 3330 # should be the last character in the word (caretOffset - 2), 3331 # work our way to the beginning of the word, stopping when 3332 # we hit another word delimiter. 3333 # 3334 wordEndOffset = offset - 1 3335 wordStartOffset = wordEndOffset 3336 3337 while wordStartOffset >= 0: 3338 [char, startOffset, endOffset] = \ 3339 text.getTextAtOffset( \ 3340 wordStartOffset, 3341 pyatspi.TEXT_BOUNDARY_CHAR) 3342 if self.utilities.isWordDelimiter(char): 3343 break 3344 else: 3345 wordStartOffset -= 1 3346 3347 # If we came across a word delimiter before hitting any 3348 # text, we really don't have a previous word. 3349 # 3350 # Otherwise, get the word. Remember we stopped when we 3351 # hit a word delimiter, so the word really starts at 3352 # wordStartOffset + 1. getText also does not include 3353 # the character at wordEndOffset, so we need to adjust 3354 # for that, too. 3355 # 3356 if wordStartOffset == wordEndOffset: 3357 return False 3358 else: 3359 word = self.utilities.\ 3360 substring(obj, wordStartOffset + 1, wordEndOffset + 1) 3361 3362 voice = self.speechGenerator.voice(string=word) 3363 word = self.utilities.adjustForRepeats(word) 3364 speech.speak(word, voice) 3365 return True 3366 3367 def sayCharacter(self, obj): 3368 """Speak the character at the caret. 3369 3370 Arguments: 3371 - obj: an Accessible object that implements the AccessibleText 3372 interface 3373 """ 3374 3375 text = obj.queryText() 3376 offset = text.caretOffset 3377 3378 # If we have selected text and the last event was a move to the 3379 # right, then speak the character to the left of where the text 3380 # caret is (i.e. the selected character). 3381 # 3382 eventString, mods = self.utilities.lastKeyAndModifiers() 3383 if (mods & keybindings.SHIFT_MODIFIER_MASK) \ 3384 and eventString in ["Right", "Down"]: 3385 offset -= 1 3386 3387 character, startOffset, endOffset = text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR) 3388 orca.emitRegionChanged(obj, startOffset, endOffset, orca.CARET_TRACKING) 3389 3390 if not character or character == '\r': 3391 character = "\n" 3392 3393 speakBlankLines = _settingsManager.getSetting('speakBlankLines') 3394 if character == "\n": 3395 line = text.getTextAtOffset(max(0, offset), 3396 pyatspi.TEXT_BOUNDARY_LINE_START) 3397 if not line[0] or line[0] == "\n": 3398 # This is a blank line. Announce it if the user requested 3399 # that blank lines be spoken. 3400 if speakBlankLines: 3401 self.speakMessage(messages.BLANK, interrupt=False) 3402 return 3403 3404 if character in ["\n", "\r\n"]: 3405 # This is a blank line. Announce it if the user requested 3406 # that blank lines be spoken. 3407 if speakBlankLines: 3408 self.speakMessage(messages.BLANK, interrupt=False) 3409 return 3410 else: 3411 self.speakMisspelledIndicator(obj, offset) 3412 self.speakCharacter(character) 3413 3414 self.pointOfReference["lastTextUnitSpoken"] = "char" 3415 3416 def sayLine(self, obj): 3417 """Speaks the line of an AccessibleText object that contains the 3418 caret, unless the line is empty in which case it's ignored. 3419 3420 Arguments: 3421 - obj: an Accessible object that implements the AccessibleText 3422 interface 3423 """ 3424 3425 [line, caretOffset, startOffset] = self.getTextLineAtCaret(obj) 3426 if len(line) and line != "\n": 3427 result = self.utilities.indentationDescription(line) 3428 if result: 3429 self.speakMessage(result) 3430 3431 endOffset = startOffset + len(line) 3432 orca.emitRegionChanged(obj, startOffset, endOffset, orca.CARET_TRACKING) 3433 3434 voice = self.speechGenerator.voice(string=line) 3435 line = self.utilities.adjustForLinks(obj, line, startOffset) 3436 line = self.utilities.adjustForRepeats(line) 3437 if self.utilities.shouldVerbalizeAllPunctuation(obj): 3438 line = self.utilities.verbalizeAllPunctuation(line) 3439 3440 utterance = [line] 3441 utterance.extend(voice) 3442 speech.speak(utterance) 3443 else: 3444 # Speak blank line if appropriate. 3445 # 3446 self.sayCharacter(obj) 3447 3448 self.pointOfReference["lastTextUnitSpoken"] = "line" 3449 3450 def sayPhrase(self, obj, startOffset, endOffset): 3451 """Speaks the text of an Accessible object between the start and 3452 end offsets, unless the phrase is empty in which case it's ignored. 3453 3454 Arguments: 3455 - obj: an Accessible object that implements the AccessibleText 3456 interface 3457 - startOffset: the start text offset. 3458 - endOffset: the end text offset. 3459 """ 3460 3461 phrase = self.utilities.expandEOCs(obj, startOffset, endOffset) 3462 if not phrase: 3463 return 3464 3465 if len(phrase) > 1 or phrase.isalnum(): 3466 result = self.utilities.indentationDescription(phrase) 3467 if result: 3468 self.speakMessage(result) 3469 3470 orca.emitRegionChanged(obj, startOffset, endOffset, orca.CARET_TRACKING) 3471 3472 voice = self.speechGenerator.voice(string=phrase) 3473 phrase = self.utilities.adjustForRepeats(phrase) 3474 if self.utilities.shouldVerbalizeAllPunctuation(obj): 3475 phrase = self.utilities.verbalizeAllPunctuation(phrase) 3476 3477 utterance = [phrase] 3478 utterance.extend(voice) 3479 speech.speak(utterance) 3480 else: 3481 self.speakCharacter(phrase) 3482 3483 self.pointOfReference["lastTextUnitSpoken"] = "phrase" 3484 3485 def sayWord(self, obj): 3486 """Speaks the word at the caret, taking into account the previous caret position.""" 3487 3488 try: 3489 text = obj.queryText() 3490 offset = text.caretOffset 3491 except: 3492 self.sayCharacter(obj) 3493 return 3494 3495 word, startOffset, endOffset = self.utilities.getWordAtOffsetAdjustedForNavigation(obj, offset) 3496 3497 # Announce when we cross a hard line boundary. 3498 if "\n" in word: 3499 if _settingsManager.getSetting('enableSpeechIndentation'): 3500 self.speakCharacter("\n") 3501 if word.startswith("\n"): 3502 startOffset += 1 3503 elif word.endswith("\n"): 3504 endOffset -= 1 3505 word = text.getText(startOffset, endOffset) 3506 3507 # sayPhrase is useful because it handles punctuation verbalization, but we don't want 3508 # to trigger its whitespace presentation. 3509 matches = list(re.finditer(r"\S+", word)) 3510 if matches: 3511 startOffset += matches[0].start() 3512 endOffset -= len(word) - matches[-1].end() 3513 word = text.getText(startOffset, endOffset) 3514 3515 msg = "DEFAULT: Final word at offset %i is '%s' (%i-%i)" \ 3516 % (offset, word.replace("\n", "\\n"), startOffset, endOffset) 3517 debug.println(debug.LEVEL_INFO, msg, True) 3518 3519 self.speakMisspelledIndicator(obj, startOffset) 3520 self.sayPhrase(obj, startOffset, endOffset) 3521 self.pointOfReference["lastTextUnitSpoken"] = "word" 3522 3523 def presentObject(self, obj, **args): 3524 interrupt = args.get("interrupt", False) 3525 self.updateBraille(obj, **args) 3526 utterances = self.speechGenerator.generateSpeech(obj, **args) 3527 speech.speak(utterances, interrupt=interrupt) 3528 3529 def stopSpeechOnActiveDescendantChanged(self, event): 3530 """Whether or not speech should be stopped prior to setting the 3531 locusOfFocus in onActiveDescendantChanged. 3532 3533 Arguments: 3534 - event: the Event 3535 3536 Returns True if speech should be stopped; False otherwise. 3537 """ 3538 3539 if not event.any_data: 3540 return True 3541 3542 # In an object which manages its descendants, the 3543 # 'descendants' may really be a single object which changes 3544 # its name. If the name-change occurs followed by the active 3545 # descendant changing (to the same object) we won't present 3546 # the locusOfFocus because it hasn't changed. Thus we need to 3547 # be sure not to cut of the presentation of the name-change 3548 # event. 3549 3550 if orca_state.locusOfFocus == event.any_data: 3551 names = self.pointOfReference.get('names', {}) 3552 oldName = names.get(hash(orca_state.locusOfFocus), '') 3553 if not oldName or event.any_data.name == oldName: 3554 return False 3555 3556 if event.source == orca_state.locusOfFocus == event.any_data.parent: 3557 return False 3558 3559 return True 3560 3561 def getFlatReviewContext(self): 3562 """Returns the flat review context, creating one if necessary.""" 3563 3564 if not self.flatReviewContext: 3565 self.flatReviewContext = flat_review.Context(self) 3566 self.justEnteredFlatReviewMode = True 3567 3568 # Remember where the cursor currently was 3569 # when the user was in focus tracking mode. We'll try to 3570 # keep the position the same as we move to characters above 3571 # and below us. 3572 # 3573 self.targetCursorCell = self.getBrailleCursorCell() 3574 3575 return self.flatReviewContext 3576 3577 def updateBrailleReview(self, targetCursorCell=0): 3578 """Obtains the braille regions for the current flat review line 3579 and displays them on the braille display. If the targetCursorCell 3580 is non-0, then an attempt will be made to position the review cursor 3581 at that cell. Otherwise, we will pan in display-sized increments 3582 to show the review cursor.""" 3583 3584 if not _settingsManager.getSetting('enableBraille') \ 3585 and not _settingsManager.getSetting('enableBrailleMonitor'): 3586 debug.println(debug.LEVEL_INFO, "BRAILLE: update review disabled", True) 3587 return 3588 3589 context = self.getFlatReviewContext() 3590 3591 [regions, regionWithFocus] = context.getCurrentBrailleRegions() 3592 if not regions: 3593 regions = [] 3594 regionWithFocus = None 3595 3596 line = self.getNewBrailleLine() 3597 self.addBrailleRegionsToLine(regions, line) 3598 braille.setLines([line]) 3599 self.setBrailleFocus(regionWithFocus, False) 3600 if regionWithFocus and not targetCursorCell: 3601 offset = regionWithFocus.brailleOffset + regionWithFocus.cursorOffset 3602 msg = "DEFAULT: Update to %i in %s" % (offset, regionWithFocus) 3603 debug.println(debug.LEVEL_INFO, msg, True) 3604 self.panBrailleToOffset(offset) 3605 3606 if self.justEnteredFlatReviewMode: 3607 self.refreshBraille(True, self.targetCursorCell) 3608 self.justEnteredFlatReviewMode = False 3609 else: 3610 self.refreshBraille(True, targetCursorCell) 3611 3612 def _setFlatReviewContextToBeginningOfBrailleDisplay(self): 3613 """Sets the character of interest to be the first character showing 3614 at the beginning of the braille display.""" 3615 3616 context = self.getFlatReviewContext() 3617 [regions, regionWithFocus] = context.getCurrentBrailleRegions() 3618 3619 # The first character on the flat review line has to be in object with text. 3620 isTextOrComponent = lambda x: isinstance(x, (braille.ReviewText, braille.ReviewComponent)) 3621 regions = list(filter(isTextOrComponent, regions)) 3622 3623 msg = "DEFAULT: Text/Component regions on line:\n%s" % "\n".join(map(str, regions)) 3624 debug.println(debug.LEVEL_INFO, msg, True) 3625 3626 # TODO - JD: The current code was stopping on the first region which met the 3627 # following condition. Is that definitely the right thing to do? Assume so for now. 3628 # Also: Should the default script be accessing things like the viewport directly?? 3629 isMatch = lambda x: x.brailleOffset + len(x.string) > braille.viewport[0] 3630 regions = list(filter(isMatch, regions)) 3631 3632 if not regions: 3633 msg = "DEFAULT: Could not find review region to move to start of display" 3634 debug.println(debug.LEVEL_INFO, msg, True) 3635 return 3636 3637 msg = "DEFAULT: Candidates for start of display:\n%s" % "\n".join(map(str, regions)) 3638 debug.println(debug.LEVEL_INFO, msg, True) 3639 3640 # TODO - JD: Again, for now we're preserving the original behavior of choosing the first. 3641 region = regions[0] 3642 position = max(region.brailleOffset, braille.viewport[0]) 3643 if region.contracted: 3644 offset = region.inPos[position - region.brailleOffset] 3645 else: 3646 offset = position - region.brailleOffset 3647 if isinstance(region.zone, flat_review.TextZone): 3648 offset += region.zone.startOffset 3649 msg = "DEFAULT: Offset for region: %i" % offset 3650 debug.println(debug.LEVEL_INFO, msg, True) 3651 3652 [word, charOffset] = region.zone.getWordAtOffset(offset) 3653 if word: 3654 msg = "DEFAULT: Setting start of display to %s, %i" % (str(word), charOffset) 3655 debug.println(debug.LEVEL_INFO, msg, True) 3656 self.flatReviewContext.setCurrent( 3657 word.zone.line.index, 3658 word.zone.index, 3659 word.index, 3660 charOffset) 3661 else: 3662 msg = "DEFAULT: Setting start of display to %s" % region.zone 3663 debug.println(debug.LEVEL_INFO, msg, True) 3664 self.flatReviewContext.setCurrent( 3665 region.zone.line.index, 3666 region.zone.index, 3667 0, # word index 3668 0) # character index 3669 3670 def find(self, query=None): 3671 """Searches for the specified query. If no query is specified, 3672 it searches for the query specified in the Orca Find dialog. 3673 3674 Arguments: 3675 - query: The search query to find. 3676 """ 3677 3678 if not query: 3679 query = find.getLastQuery() 3680 if query: 3681 context = self.getFlatReviewContext() 3682 location = query.findQuery(context, self.justEnteredFlatReviewMode) 3683 if not location: 3684 self.presentMessage(messages.STRING_NOT_FOUND) 3685 else: 3686 context.setCurrent(location.lineIndex, location.zoneIndex, \ 3687 location.wordIndex, location.charIndex) 3688 self.reviewCurrentItem(None) 3689 self.targetCursorCell = self.getBrailleCursorCell() 3690 3691 def textLines(self, obj, offset=None): 3692 """Creates a generator that can be used to iterate over each line 3693 of a text object, starting at the caret offset. 3694 3695 Arguments: 3696 - obj: an Accessible that has a text specialization 3697 3698 Returns an iterator that produces elements of the form: 3699 [SayAllContext, acss], where SayAllContext has the text to be 3700 spoken and acss is an ACSS instance for speaking the text. 3701 """ 3702 3703 self._sayAllIsInterrupted = False 3704 try: 3705 text = obj.queryText() 3706 except: 3707 self._inSayAll = False 3708 self._sayAllContexts = [] 3709 return 3710 3711 self._inSayAll = True 3712 length = text.characterCount 3713 if offset is None: 3714 offset = text.caretOffset 3715 3716 # Determine the correct "say all by" mode to use. 3717 # 3718 sayAllStyle = _settingsManager.getSetting('sayAllStyle') 3719 if sayAllStyle == settings.SAYALL_STYLE_SENTENCE: 3720 mode = pyatspi.TEXT_BOUNDARY_SENTENCE_START 3721 elif sayAllStyle == settings.SAYALL_STYLE_LINE: 3722 mode = pyatspi.TEXT_BOUNDARY_LINE_START 3723 else: 3724 mode = pyatspi.TEXT_BOUNDARY_LINE_START 3725 3726 priorObj = obj 3727 3728 # Get the next line of text to read 3729 # 3730 done = False 3731 while not done: 3732 speech.speak(self.speechGenerator.generateContext(obj, priorObj=priorObj)) 3733 3734 lastEndOffset = -1 3735 while offset < length: 3736 [lineString, startOffset, endOffset] = text.getTextAtOffset( 3737 offset, mode) 3738 3739 # Some applications that don't support sentence boundaries 3740 # will provide the line boundary results instead; others 3741 # will return nothing. 3742 # 3743 if not lineString: 3744 mode = pyatspi.TEXT_BOUNDARY_LINE_START 3745 [lineString, startOffset, endOffset] = \ 3746 text.getTextAtOffset(offset, mode) 3747 3748 if endOffset > text.characterCount: 3749 msg = "WARNING: endOffset: %i > characterCount: %i " \ 3750 " resulting from text.getTextAtOffset(%i, %s) for %s" \ 3751 % (endOffset, text.characterCount, offset, mode, obj) 3752 debug.println(debug.LEVEL_INFO, msg, True) 3753 endOffset = text.characterCount 3754 3755 # [[[WDW - HACK: this is here because getTextAtOffset 3756 # tends not to be implemented consistently across toolkits. 3757 # Sometimes it behaves properly (i.e., giving us an endOffset 3758 # that is the beginning of the next line), sometimes it 3759 # doesn't (e.g., giving us an endOffset that is the end of 3760 # the current line). So...we hack. The whole 'max' deal 3761 # is to account for lines that might be a brazillion lines 3762 # long.]]] 3763 # 3764 if endOffset == lastEndOffset: 3765 offset = max(offset + 1, lastEndOffset + 1) 3766 lastEndOffset = endOffset 3767 continue 3768 3769 lastEndOffset = endOffset 3770 offset = endOffset 3771 3772 voice = self.speechGenerator.voice(string=lineString) 3773 if voice and isinstance(voice, list): 3774 voice = voice[0] 3775 3776 lineString = \ 3777 self.utilities.adjustForLinks(obj, lineString, startOffset) 3778 lineString = self.utilities.adjustForRepeats(lineString) 3779 3780 context = speechserver.SayAllContext( 3781 obj, lineString, startOffset, endOffset) 3782 msg = "DEFAULT %s" % context 3783 debug.println(debug.LEVEL_INFO, msg, True) 3784 self._sayAllContexts.append(context) 3785 eventsynthesizer.scrollIntoView(obj, startOffset, endOffset) 3786 yield [context, voice] 3787 3788 moreLines = False 3789 relations = obj.getRelationSet() 3790 for relation in relations: 3791 if relation.getRelationType() == pyatspi.RELATION_FLOWS_TO: 3792 priorObj = obj 3793 obj = relation.getTarget(0) 3794 3795 try: 3796 text = obj.queryText() 3797 except NotImplementedError: 3798 return 3799 3800 length = text.characterCount 3801 offset = 0 3802 moreLines = True 3803 break 3804 if not moreLines: 3805 done = True 3806 3807 self._inSayAll = False 3808 self._sayAllContexts = [] 3809 3810 msg = "DEFAULT: textLines complete. Verifying SayAll status" 3811 debug.println(debug.LEVEL_INFO, msg, True) 3812 self.inSayAll() 3813 3814 def getTextLineAtCaret(self, obj, offset=None, startOffset=None, endOffset=None): 3815 """To-be-removed. Returns the string, caretOffset, startOffset.""" 3816 3817 try: 3818 text = obj.queryText() 3819 offset = text.caretOffset 3820 characterCount = text.characterCount 3821 except NotImplementedError: 3822 return ["", 0, 0] 3823 except: 3824 msg = "DEFAULT: Exception getting offset and length for %s" % obj 3825 debug.println(debug.LEVEL_INFO, msg, True) 3826 return ["", 0, 0] 3827 3828 targetOffset = startOffset 3829 if targetOffset is None: 3830 targetOffset = max(0, offset) 3831 3832 # The offset might be positioned at the very end of the text area. 3833 # In these cases, calling text.getTextAtOffset on an offset that's 3834 # not positioned to a character can yield unexpected results. In 3835 # particular, we'll see the Gecko toolkit return a start and end 3836 # offset of (0, 0), and we'll see other implementations, such as 3837 # gedit, return reasonable results (i.e., gedit will give us the 3838 # last line). 3839 # 3840 # In order to accommodate the differing behavior of different 3841 # AT-SPI implementations, we'll make sure we give getTextAtOffset 3842 # the offset of an actual character. Then, we'll do a little check 3843 # to see if that character is a newline - if it is, we'll treat it 3844 # as the line. 3845 # 3846 if targetOffset == characterCount: 3847 fixedTargetOffset = max(0, targetOffset - 1) 3848 character = text.getText(fixedTargetOffset, fixedTargetOffset + 1) 3849 else: 3850 fixedTargetOffset = targetOffset 3851 character = None 3852 3853 if (targetOffset == characterCount) \ 3854 and (character == "\n"): 3855 lineString = "" 3856 startOffset = fixedTargetOffset 3857 else: 3858 # Get the line containing the caret. [[[TODO: HACK WDW - If 3859 # there's only 1 character in the string, well, we get it. We 3860 # do this because Gecko's implementation of getTextAtOffset 3861 # is broken if there is just one character in the string.]]] 3862 # 3863 if (characterCount == 1): 3864 lineString = text.getText(fixedTargetOffset, fixedTargetOffset + 1) 3865 startOffset = fixedTargetOffset 3866 else: 3867 if fixedTargetOffset == -1: 3868 fixedTargetOffset = characterCount 3869 try: 3870 [lineString, startOffset, endOffset] = text.getTextAtOffset( 3871 fixedTargetOffset, pyatspi.TEXT_BOUNDARY_LINE_START) 3872 except: 3873 return ["", 0, 0] 3874 3875 # Sometimes we get the trailing line-feed-- remove it 3876 # It is important that these are in order. 3877 # In some circumstances we might get: 3878 # word word\r\n 3879 # so remove \n, and then remove \r. 3880 # See bgo#619332. 3881 # 3882 lineString = lineString.rstrip('\n') 3883 lineString = lineString.rstrip('\r') 3884 3885 return [lineString, text.caretOffset, startOffset] 3886 3887 def phoneticSpellCurrentItem(self, itemString): 3888 """Phonetically spell the current flat review word or line. 3889 3890 Arguments: 3891 - itemString: the string to phonetically spell. 3892 """ 3893 3894 for (charIndex, character) in enumerate(itemString): 3895 voice = self.speechGenerator.voice(string=character) 3896 phoneticString = phonnames.getPhoneticName(character.lower()) 3897 speech.speak(phoneticString, voice) 3898 3899 def _saveLastCursorPosition(self, obj, caretOffset): 3900 """Save away the current text cursor position for next time. 3901 3902 Arguments: 3903 - obj: the current accessible 3904 - caretOffset: the cursor position within this object 3905 """ 3906 3907 prevObj, prevOffset = self.pointOfReference.get("lastCursorPosition", (None, -1)) 3908 self.pointOfReference["penultimateCursorPosition"] = prevObj, prevOffset 3909 self.pointOfReference["lastCursorPosition"] = obj, caretOffset 3910 3911 def systemBeep(self): 3912 """Rings the system bell. This is really a hack. Ideally, we want 3913 a method that will present an earcon (any sound designated for the 3914 purpose of representing an error, event etc) 3915 """ 3916 3917 print("\a") 3918 3919 def speakMisspelledIndicator(self, obj, offset): 3920 """Speaks an announcement indicating that a given word is misspelled. 3921 3922 Arguments: 3923 - obj: An accessible which implements the accessible text interface. 3924 - offset: Offset in the accessible's text for which to retrieve the 3925 attributes. 3926 """ 3927 3928 if _settingsManager.getSetting('speakMisspelledIndicator'): 3929 try: 3930 text = obj.queryText() 3931 except: 3932 return 3933 # If we're on whitespace, we cannot be on a misspelled word. 3934 # 3935 charAndOffsets = \ 3936 text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR) 3937 if not charAndOffsets[0].strip() \ 3938 or self.utilities.isWordDelimiter(charAndOffsets[0]): 3939 self._lastWordCheckedForSpelling = charAndOffsets[0] 3940 return 3941 3942 wordAndOffsets = \ 3943 text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_WORD_START) 3944 if self.utilities.isWordMisspelled(obj, offset) \ 3945 and wordAndOffsets[0] != self._lastWordCheckedForSpelling: 3946 self.speakMessage(messages.MISSPELLED) 3947 # Store this word so that we do not continue to present the 3948 # presence of the red squiggly as the user arrows amongst 3949 # the characters. 3950 # 3951 self._lastWordCheckedForSpelling = wordAndOffsets[0] 3952 3953 ############################################################################ 3954 # # 3955 # Presentation methods # 3956 # (scripts should not call methods in braille.py or speech.py directly) # 3957 # # 3958 ############################################################################ 3959 3960 def presentationInterrupt(self): 3961 """Convenience method to interrupt presentation of whatever is being 3962 presented at the moment.""" 3963 3964 msg = "DEFAULT: Interrupting presentation" 3965 debug.println(debug.LEVEL_INFO, msg, True) 3966 speech.stop() 3967 braille.killFlash() 3968 3969 def presentKeyboardEvent(self, event): 3970 """Convenience method to present the KeyboardEvent event. Returns True 3971 if we fully present the event; False otherwise.""" 3972 3973 if not event.isPressedKey(): 3974 self._sayAllIsInterrupted = False 3975 self.utilities.clearCachedCommandState() 3976 3977 if not orca_state.learnModeEnabled: 3978 if event.shouldEcho == False or event.isOrcaModified(): 3979 return False 3980 3981 try: 3982 role = orca_state.locusOfFocus.getRole() 3983 except: 3984 return False 3985 3986 if role in [pyatspi.ROLE_DIALOG, pyatspi.ROLE_FRAME, pyatspi.ROLE_WINDOW]: 3987 focusedObject = self.utilities.focusedObject(orca_state.activeWindow) 3988 if focusedObject: 3989 orca.setLocusOfFocus(None, focusedObject, False) 3990 role = focusedObject.getRole() 3991 3992 if role == pyatspi.ROLE_PASSWORD_TEXT and not event.isLockingKey(): 3993 return False 3994 3995 if not event.isPressedKey(): 3996 return False 3997 3998 braille.displayKeyEvent(event) 3999 orcaModifierPressed = event.isOrcaModifier() and event.isPressedKey() 4000 if event.isCharacterEchoable() and not orcaModifierPressed: 4001 return False 4002 if orca_state.learnModeEnabled: 4003 if event.isPrintableKey() and event.getClickCount() == 2: 4004 self.phoneticSpellCurrentItem(event.event_string) 4005 return True 4006 4007 string = None 4008 if event.isPrintableKey(): 4009 string = event.event_string 4010 4011 msg = "DEFAULT: Presenting keyboard event" 4012 debug.println(debug.LEVEL_INFO, msg, True) 4013 4014 voice = self.speechGenerator.voice(string=string) 4015 speech.speakKeyEvent(event, voice) 4016 return True 4017 4018 def presentMessage(self, fullMessage, briefMessage=None, voice=None, resetStyles=True, force=False): 4019 """Convenience method to speak a message and 'flash' it in braille. 4020 4021 Arguments: 4022 - fullMessage: This can be a string or a list. This will be presented 4023 as the message for users whose flash or message verbosity level is 4024 verbose. 4025 - briefMessage: This can be a string or a list. This will be presented 4026 as the message for users whose flash or message verbosity level is 4027 brief. Note that providing no briefMessage will result in the full 4028 message being used for either. Callers wishing to present nothing as 4029 the briefMessage should set briefMessage to an empty string. 4030 - voice: The voice to use when speaking this message. By default, the 4031 "system" voice will be used. 4032 """ 4033 4034 if not fullMessage: 4035 return 4036 4037 if briefMessage is None: 4038 briefMessage = fullMessage 4039 4040 if _settingsManager.getSetting('enableSpeech'): 4041 if not _settingsManager.getSetting('messagesAreDetailed'): 4042 message = briefMessage 4043 else: 4044 message = fullMessage 4045 if message: 4046 self.speakMessage(message, voice=voice, resetStyles=resetStyles, force=force) 4047 4048 if (_settingsManager.getSetting('enableBraille') \ 4049 or _settingsManager.getSetting('enableBrailleMonitor')) \ 4050 and _settingsManager.getSetting('enableFlashMessages'): 4051 if not _settingsManager.getSetting('flashIsDetailed'): 4052 message = briefMessage 4053 else: 4054 message = fullMessage 4055 if not message: 4056 return 4057 4058 if isinstance(message[0], list): 4059 message = message[0] 4060 if isinstance(message, list): 4061 message = [i for i in message if isinstance(i, str)] 4062 message = " ".join(message) 4063 4064 if _settingsManager.getSetting('flashIsPersistent'): 4065 duration = -1 4066 else: 4067 duration = _settingsManager.getSetting('brailleFlashTime') 4068 4069 braille.displayMessage(message, flashTime=duration) 4070 4071 def idleMessage(self): 4072 """Convenience method to tell speech and braille engines to hand off 4073 control to other screen readers.""" 4074 4075 braille.disableBraille() 4076 4077 @staticmethod 4078 def __play(sounds, interrupt=True): 4079 if not sounds: 4080 return 4081 4082 if not isinstance(sounds, list): 4083 icon = [sounds] 4084 4085 _player = sound.getPlayer() 4086 _player.play(sounds[0], interrupt) 4087 for i in range(1, len(sounds)): 4088 sound.play(sounds[i], interrupt=False) 4089 4090 @staticmethod 4091 def addBrailleRegionToLine(region, line): 4092 """Adds the braille region to the line. 4093 4094 Arguments: 4095 - region: a braille.Region (e.g. what is returned by the braille 4096 generator's generateBraille() method. 4097 - line: a braille.Line 4098 """ 4099 4100 line.addRegion(region) 4101 4102 @staticmethod 4103 def addBrailleRegionsToLine(regions, line): 4104 """Adds the braille region to the line. 4105 4106 Arguments: 4107 - regions: a series of braille.Region instances (a single instance 4108 being what is returned by the braille generator's generateBraille() 4109 method. 4110 - line: a braille.Line 4111 """ 4112 4113 line.addRegions(regions) 4114 4115 @staticmethod 4116 def addToLineAsBrailleRegion(string, line): 4117 """Creates a Braille Region out of string and adds it to the line. 4118 4119 Arguments: 4120 - string: the string to be displayed 4121 - line: a braille.Line 4122 """ 4123 4124 line.addRegion(braille.Region(string)) 4125 4126 @staticmethod 4127 def brailleRegionsFromStrings(strings): 4128 """Creates a list of braille regions from the list of strings. 4129 4130 Arguments: 4131 - strings: a list of strings from which to create the list of 4132 braille Region instances 4133 4134 Returns the list of braille Region instances 4135 """ 4136 4137 brailleRegions = [] 4138 for string in strings: 4139 brailleRegions.append(braille.Region(string)) 4140 4141 return brailleRegions 4142 4143 @staticmethod 4144 def clearBraille(): 4145 """Clears the logical structure, but keeps the Braille display as is 4146 (until a refresh operation).""" 4147 4148 braille.clear() 4149 4150 @staticmethod 4151 def displayBrailleMessage(message, cursor=-1, flashTime=0): 4152 """Displays a single line, setting the cursor to the given position, 4153 ensuring that the cursor is in view. 4154 4155 Arguments: 4156 - message: the string to display 4157 - cursor: the 0-based cursor position, where -1 (default) means no 4158 cursor 4159 - flashTime: if non-0, the number of milliseconds to display the 4160 regions before reverting back to what was there before. A 0 means 4161 to not do any flashing. A negative number means to display the 4162 message until some other message comes along or the user presses 4163 a cursor routing key. 4164 """ 4165 4166 if not _settingsManager.getSetting('enableBraille') \ 4167 and not _settingsManager.getSetting('enableBrailleMonitor'): 4168 debug.println(debug.LEVEL_INFO, "BRAILLE: display message disabled", True) 4169 return 4170 4171 braille.displayMessage(message, cursor, flashTime) 4172 4173 @staticmethod 4174 def displayBrailleRegions(regionInfo, flashTime=0): 4175 """Displays a list of regions on a single line, setting focus to the 4176 specified region. The regionInfo parameter is something that is 4177 typically returned by a call to braille_generator.generateBraille. 4178 4179 Arguments: 4180 - regionInfo: a list where the first element is a list of regions 4181 to display and the second element is the region with focus (must 4182 be in the list from element 0) 4183 - flashTime: if non-0, the number of milliseconds to display the 4184 regions before reverting back to what was there before. A 0 means 4185 to not do any flashing. A negative number means to display the 4186 message until some other message comes along or the user presses 4187 a cursor routing key. 4188 """ 4189 4190 if not _settingsManager.getSetting('enableBraille') \ 4191 and not _settingsManager.getSetting('enableBrailleMonitor'): 4192 debug.println(debug.LEVEL_INFO, "BRAILLE: display regions disabled", True) 4193 return 4194 4195 braille.displayRegions(regionInfo, flashTime) 4196 4197 def displayBrailleForObject(self, obj): 4198 """Convenience method for scripts combining the call to the braille 4199 generator for the script with the call to displayBrailleRegions. 4200 4201 Arguments: 4202 - obj: the accessible object to display in braille 4203 """ 4204 4205 regions = self.brailleGenerator.generateBraille(obj) 4206 self.displayBrailleRegions(regions) 4207 4208 @staticmethod 4209 def getBrailleCaretContext(event): 4210 """Gets the accesible and caret offset associated with the given 4211 event. The event should have a BrlAPI event that contains an 4212 argument value that corresponds to a cell on the display. 4213 4214 Arguments: 4215 - event: an instance of input_event.BrailleEvent. event.event is 4216 the dictionary form of the expanded BrlAPI event. 4217 """ 4218 4219 return braille.getCaretContext(event) 4220 4221 @staticmethod 4222 def getBrailleCursorCell(): 4223 """Returns the value of position of the braille cell which has the 4224 cursor. A value of 0 means no cell has the cursor.""" 4225 4226 return braille.cursorCell 4227 4228 @staticmethod 4229 def getNewBrailleLine(clearBraille=False, addLine=False): 4230 """Creates a new braille Line. 4231 4232 Arguments: 4233 - clearBraille: Whether the display should be cleared. 4234 - addLine: Whether the line should be added to the logical display 4235 for painting. 4236 4237 Returns the new Line. 4238 """ 4239 4240 if clearBraille: 4241 braille.clear() 4242 line = braille.Line() 4243 if addLine: 4244 braille.addLine(line) 4245 4246 return line 4247 4248 @staticmethod 4249 def getNewBrailleComponent(accessible, string, cursorOffset=0, 4250 indicator='', expandOnCursor=False): 4251 """Creates a new braille Component. 4252 4253 Arguments: 4254 - accessible: the accessible associated with this region 4255 - string: the string to be displayed 4256 - cursorOffset: a 0-based index saying where to draw the cursor 4257 for this Region if it gets focus 4258 4259 Returns the new Component. 4260 """ 4261 4262 return braille.Component(accessible, string, cursorOffset, 4263 indicator, expandOnCursor) 4264 4265 @staticmethod 4266 def getNewBrailleRegion(string, cursorOffset=0, expandOnCursor=False): 4267 """Creates a new braille Region. 4268 4269 Arguments: 4270 - string: the string to be displayed 4271 - cursorOffset: a 0-based index saying where to draw the cursor 4272 for this Region if it gets focus 4273 4274 Returns the new Region. 4275 """ 4276 4277 return braille.Region(string, cursorOffset, expandOnCursor) 4278 4279 @staticmethod 4280 def getNewBrailleText(accessible, label="", eol="", startOffset=None, 4281 endOffset=None): 4282 4283 """Creates a new braille Text region. 4284 4285 Arguments: 4286 - accessible: the accessible associated with this region and which 4287 implements AtkText 4288 - label: an optional label to display 4289 - eol: the endOfLine indicator 4290 4291 Returns the new Text region. 4292 """ 4293 4294 return braille.Text(accessible, label, eol, startOffset, endOffset) 4295 4296 @staticmethod 4297 def isBrailleBeginningShowing(): 4298 """If True, the beginning of the line is showing on the braille 4299 display.""" 4300 4301 return braille.beginningIsShowing 4302 4303 @staticmethod 4304 def isBrailleEndShowing(): 4305 """If True, the end of the line is showing on the braille display.""" 4306 4307 return braille.endIsShowing 4308 4309 @staticmethod 4310 def panBrailleInDirection(panAmount=0, panToLeft=True): 4311 """Pans the display to the left, limiting the pan to the beginning 4312 of the line being displayed. 4313 4314 Arguments: 4315 - panAmount: the amount to pan. A value of 0 means the entire 4316 width of the physical display. 4317 - panToLeft: if True, pan to the left; otherwise to the right 4318 4319 Returns True if a pan actually happened. 4320 """ 4321 4322 if panToLeft: 4323 return braille.panLeft(panAmount) 4324 else: 4325 return braille.panRight(panAmount) 4326 4327 @staticmethod 4328 def panBrailleToOffset(offset): 4329 """Automatically pan left or right to make sure the current offset 4330 is showing.""" 4331 4332 braille.panToOffset(offset) 4333 4334 @staticmethod 4335 def presentItemsInBraille(items): 4336 """Method to braille a list of items. Scripts should call this 4337 method rather than handling the creation and displaying of a 4338 braille line directly. 4339 4340 Arguments: 4341 - items: a list of strings to be presented 4342 """ 4343 4344 line = braille.getShowingLine() 4345 for item in items: 4346 line.addRegion(braille.Region(" " + item)) 4347 4348 braille.refresh() 4349 4350 def updateBrailleForNewCaretPosition(self, obj): 4351 """Try to reposition the cursor without having to do a full update.""" 4352 4353 if not _settingsManager.getSetting('enableBraille') \ 4354 and not _settingsManager.getSetting('enableBrailleMonitor'): 4355 debug.println(debug.LEVEL_INFO, "BRAILLE: update caret disabled", True) 4356 return 4357 4358 brailleNeedsRepainting = True 4359 line = braille.getShowingLine() 4360 for region in line.regions: 4361 if isinstance(region, braille.Text) and region.accessible == obj: 4362 if region.repositionCursor(): 4363 self.refreshBraille(True) 4364 brailleNeedsRepainting = False 4365 break 4366 4367 if brailleNeedsRepainting: 4368 self.updateBraille(obj) 4369 4370 @staticmethod 4371 def refreshBraille(panToCursor=True, targetCursorCell=0, getLinkMask=True, 4372 stopFlash=True): 4373 """This is the method scripts should use to refresh braille rather 4374 than calling self.refreshBraille() directly. The intent is to centralize 4375 such calls into as few places as possible so that we can easily and 4376 safely not perform braille-related functions for users who do not 4377 have braille and/or the braille monitor enabled. 4378 4379 Arguments: 4380 4381 - panToCursor: if True, will adjust the viewport so the cursor is 4382 showing. 4383 - targetCursorCell: Only effective if panToCursor is True. 4384 0 means automatically place the cursor somewhere on the display so 4385 as to minimize movement but show as much of the line as possible. 4386 A positive value is a 1-based target cell from the left side of 4387 the display and a negative value is a 1-based target cell from the 4388 right side of the display. 4389 - getLinkMask: Whether or not we should take the time to get the 4390 attributeMask for links. Reasons we might not want to include 4391 knowing that we will fail and/or it taking an unreasonable 4392 amount of time (AKA Gecko). 4393 - stopFlash: if True, kill any flashed message that may be showing. 4394 """ 4395 4396 braille.refresh(panToCursor, targetCursorCell, getLinkMask, stopFlash) 4397 4398 @staticmethod 4399 def setBrailleFocus(region, panToFocus=True, getLinkMask=True): 4400 """Specififes the region with focus. This region will be positioned 4401 at the home position if panToFocus is True. 4402 4403 Arguments: 4404 - region: the given region, which much be in a line that has been 4405 added to the logical display 4406 - panToFocus: whether or not to position the region at the home 4407 position 4408 - getLinkMask: Whether or not we should take the time to get the 4409 attributeMask for links. Reasons we might not want to include 4410 knowing that we will fail and/or it taking an unreasonable 4411 amount of time (AKA Gecko). 4412 """ 4413 4414 braille.setFocus(region, panToFocus, getLinkMask) 4415 4416 @staticmethod 4417 def _setContractedBraille(event): 4418 """Turns contracted braille on or off based upon the event. 4419 4420 Arguments: 4421 - event: an instance of input_event.BrailleEvent. event.event is 4422 the dictionary form of the expanded BrlAPI event. 4423 """ 4424 4425 braille.setContractedBraille(event) 4426 4427 ######################################################################## 4428 # # 4429 # Speech methods # 4430 # (scripts should not call methods in speech.py directly) # 4431 # # 4432 ######################################################################## 4433 4434 def speakCharacter(self, character): 4435 """Method to speak a single character. Scripts should use this 4436 method rather than calling speech.speakCharacter directly.""" 4437 4438 voice = self.speechGenerator.voice(string=character) 4439 speech.speakCharacter(character, voice) 4440 4441 def speakMessage(self, string, voice=None, interrupt=True, resetStyles=True, force=False): 4442 """Method to speak a single string. Scripts should use this 4443 method rather than calling speech.speak directly. 4444 4445 - string: The string to be spoken. 4446 - voice: The voice to use. By default, the "system" voice will 4447 be used. 4448 - interrupt: If True, any current speech should be interrupted 4449 prior to speaking the new text. 4450 """ 4451 4452 if not _settingsManager.getSetting('enableSpeech') \ 4453 or (_settingsManager.getSetting('onlySpeakDisplayedText') and not force): 4454 return 4455 4456 voices = _settingsManager.getSetting('voices') 4457 systemVoice = voices.get(settings.SYSTEM_VOICE) 4458 4459 voice = voice or systemVoice 4460 if voice == systemVoice and resetStyles: 4461 capStyle = _settingsManager.getSetting('capitalizationStyle') 4462 _settingsManager.setSetting('capitalizationStyle', settings.CAPITALIZATION_STYLE_NONE) 4463 speech.updateCapitalizationStyle() 4464 4465 punctStyle = _settingsManager.getSetting('verbalizePunctuationStyle') 4466 _settingsManager.setSetting('verbalizePunctuationStyle', settings.PUNCTUATION_STYLE_NONE) 4467 speech.updatePunctuationLevel() 4468 4469 speech.speak(string, voice, interrupt) 4470 4471 if voice == systemVoice and resetStyles: 4472 _settingsManager.setSetting('capitalizationStyle', capStyle) 4473 speech.updateCapitalizationStyle() 4474 4475 _settingsManager.setSetting('verbalizePunctuationStyle', punctStyle) 4476 speech.updatePunctuationLevel() 4477 4478 @staticmethod 4479 def presentItemsInSpeech(items): 4480 """Method to speak a list of items. Scripts should call this 4481 method rather than handling the creation and speaking of 4482 utterances directly. 4483 4484 Arguments: 4485 - items: a list of strings to be presented 4486 """ 4487 4488 utterances = [] 4489 for item in items: 4490 utterances.append(item) 4491 4492 speech.speak(utterances) 4493 4494 def speakUnicodeCharacter(self, character): 4495 """ Speaks some information about an unicode character. 4496 At the moment it just announces the character unicode number but 4497 this information may be changed in the future 4498 4499 Arguments: 4500 - character: the character to speak information of 4501 """ 4502 speech.speak(messages.UNICODE % \ 4503 self.utilities.unicodeValueString(character)) 4504 4505 def presentTime(self, inputEvent): 4506 """ Presents the current time. """ 4507 timeFormat = _settingsManager.getSetting('presentTimeFormat') 4508 message = time.strftime(timeFormat, time.localtime()) 4509 self.presentMessage(message) 4510 return True 4511 4512 def presentDate(self, inputEvent): 4513 """ Presents the current date. """ 4514 dateFormat = _settingsManager.getSetting('presentDateFormat') 4515 message = time.strftime(dateFormat, time.localtime()) 4516 self.presentMessage(message) 4517 return True 4518 4519 def presentSizeAndPosition(self, inputEvent): 4520 """ Presents the size and position of the locusOfFocus. """ 4521 4522 if self.flatReviewContext: 4523 obj = self.flatReviewContext.getCurrentAccessible() 4524 else: 4525 obj = orca_state.locusOfFocus 4526 4527 x, y, width, height = self.utilities.getBoundingBox(obj) 4528 if (x, y, width, height) == (-1, -1, 0, 0): 4529 full = messages.LOCATION_NOT_FOUND_FULL 4530 brief = messages.LOCATION_NOT_FOUND_BRIEF 4531 self.presentMessage(full, brief) 4532 return True 4533 4534 full = messages.SIZE_AND_POSITION_FULL % (width, height, x, y) 4535 brief = messages.SIZE_AND_POSITION_BRIEF % (width, height, x, y) 4536 self.presentMessage(full, brief) 4537 return True 4538