# Orca # # Copyright 2005-2008 Sun Microsystems Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the # Free Software Foundation, Inc., Franklin Street, Fifth Floor, # Boston MA 02110-1301 USA. """The gnome-terminal script mainly handles the unique interaction model of manipulating text - both the user and the system put text into the system and we try to determine which was which and why.""" __id__ = "$Id$" __version__ = "$Revision$" __date__ = "$Date$" __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." __license__ = "LGPL" import pyatspi import orca.scripts.default as default import orca.input_event as input_event import orca.orca as orca import orca.orca_state as orca_state import orca.settings as settings import orca.speech as speech _settingsManager = getattr(orca, '_settingsManager') ######################################################################## # # # The GnomeTerminal script class. # # # ######################################################################## class Script(default.Script): def __init__(self, app): """Creates a new script for the given application. Arguments: - app: the application to create a script for. """ default.Script.__init__(self, app) # By default, don't present if gnome-terminal is not the active # application. # self.presentIfInactive = False def echoKey(self, keyEvent): """Determine whether this script should echo the current key event. If this is a printable key, then return False. Note that the keyEcho() method in orca.py will still take into account whatever the user's various preferences for key echoing are, which may override what is return by this echoKey() method. Arguments: - keyEvent - the key event Returns an indication of whether a key echo event should be allowed to happen for this script. """ if orca.isPrintableKey(keyEvent.event_string): return False return True def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus): """Called when the visual object with focus changes. Arguments: - event: if not None, the Event that caused the change - oldLocusOfFocus: Accessible that is the old locus of focus - newLocusOfFocus: Accessible that is the new locus of focus """ # If the new locus of focus has a role of "terminal", then update # the braille display accordingly. Also speak the page tab that this # terminal is in if it's sensitive (i.e. there are two or more tabs) # and if the old locus of focus also had a "terminal role. # See bug #518762 for more details. # if newLocusOfFocus and \ newLocusOfFocus.getRole() == pyatspi.ROLE_TERMINAL: pageTab = event.source.parent.parent.parent if oldLocusOfFocus \ and oldLocusOfFocus.getRole() == pyatspi.ROLE_TERMINAL and \ pageTab.getRole() == pyatspi.ROLE_PAGE_TAB and \ pageTab.getState().contains(pyatspi.STATE_SENSITIVE): self.updateBraille(newLocusOfFocus) utterances = self.speechGenerator.generateSpeech(pageTab) speech.speak(utterances) default.Script.locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus) #def onWindowActivated(self, event): # # Sets the context to the top level window first, so we can # # get information about it the window we just moved to. # # # orca.setLocusOfFocus(event, event.source) # # # Now we find the focused object and set the locus of focus to it. # # # obj = self.findFocusedObject(self.app) # if obj: # orca.setLocusOfFocus(event, obj) # else: # default.Script.onWindowActivated(self, event) def onTextDeleted(self, event): """Called whenever text is deleted from an object. Arguments: - event: the Event """ event_string, mods = self.utilities.lastKeyAndModifiers() # We only do special things when people press backspace # in terminals. # if (event.source.getRole() != pyatspi.ROLE_TERMINAL) \ or (event_string != "BackSpace"): default.Script.onTextDeleted(self, event) return # Ignore text deletions from non-focused objects, unless the # currently focused object is the parent of the object from which # text was deleted. # if (event.source != orca_state.locusOfFocus) \ and (event.source.parent != orca_state.locusOfFocus): return self.updateBraille(event.source) # Speak the character that has just been deleted. # character = event.any_data if character.decode("UTF-8").isupper(): voice = self.voices[settings.UPPERCASE_VOICE] else: voice = self.voices[settings.DEFAULT_VOICE] if len(character.decode('utf-8')) == 1: speech.speakCharacter(character, voice) else: speech.speak(character, voice, False) def onTextInserted(self, event): """Called whenever text is inserted into an object. Arguments: - event: the Event """ # We only do special things for terminals. # if event.source.getRole() != pyatspi.ROLE_TERMINAL: default.Script.onTextInserted(self, event) return # Ignore text insertions from non-focused objects, unless the # currently focused object is the parent of the object from which # text was inserted. # if (event.source != orca_state.locusOfFocus) \ and (event.source.parent != orca_state.locusOfFocus): return self.updateBraille(event.source) text = event.any_data # When one does a delete in a terminal, the remainder of the # line is "inserted" instead of being shifted left. We will # detect this by seeing if the keystring was a delete action. # If we run into this case, we don't really want to speak the # rest of the line. # # We'll let our super class handle "Delete". We'll handle Ctrl+D. # if not orca_state.lastInputEvent: return matchFound = False speakThis = False if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent): keyString, mods = self.utilities.lastKeyAndModifiers() controlPressed = mods & settings.CTRL_MODIFIER_MASK if keyString in ["Delete", "BackSpace"]: return elif (keyString == "D") and controlPressed: text = text.decode("UTF-8")[0].decode("UTF-8") # If the last input event was a keyboard event, check to see if # the text for this event matches what the user typed. If it does, # then call orca.keyEcho() to echo it (based on the user's key # echo preferences). # # Note that the text widgets sometimes compress their events, # thus we might get a longer string from a single text inserted # event, while we also get individual keyboard events for the # characters used to type the string. This is ugly. We attempt # to handle it here by only echoing text if we think it was the # result of a command (e.g., a paste operation). # # Note that we have to special case the space character as it # comes across as "space" in the keyboard event and " " in the # text event. # # For terminal, Return usually ends up in more text from the # system, which we want to hear. Tab is also often used for # command line completion, so we want to hear that, too. # # Finally, if we missed some command and the system is giving # us a string typically longer than what the length of a # compressed string is (we choose 5 here), then output that. # wasCommand = controlPressed or keyString in ["Return", "Tab"] if (text == " " and keyString == "space") or text == keyString: matchFound = True elif wasCommand or (len(text) > 5): speakThis = True elif isinstance(orca_state.lastInputEvent, \ input_event.MouseButtonEvent) and \ orca_state.lastInputEvent.button == "2": speakThis = True if matchFound: echoed = orca.keyEcho(orca_state.lastInputEvent) else: echoed = False if not echoed: # We might need to echo this if it is a single character. # speakThis = speakThis \ or ((_settingsManager.getSetting('enableEchoByCharacter') \ or (_settingsManager.getSetting('enableKeyEcho') \ and _settingsManager.getSetting('enablePrintableKeys'))) \ and text \ and event.source.getRole() \ != pyatspi.ROLE_PASSWORD_TEXT \ and len(text.decode("UTF-8")) == 1) if speakThis: if text.decode("UTF-8").isupper(): speech.speak(text, self.voices[settings.UPPERCASE_VOICE]) else: speech.speak(text) if _settingsManager.getSetting('enableEchoByWord') \ and self.utilities.isWordDelimiter(text.decode("UTF-8")[-1:]): if matchFound: self.echoPreviousWord(event.source) def getTextLineAtCaret(self, acc, offset=None): """Gets the line of text where the caret is. Argument: - obj: an Accessible object that implements the AccessibleText interface - offset: an optional caret offset to use. Returns the [string, caretOffset, startOffset] for the line of text where the caret is. """ string, caretOffset, lineOffset = \ default.Script.getTextLineAtCaret(self, acc) # Sometimes, gnome-terminal will give us very odd values when # the user is editing using 'vi' and has positioned the caret # at the first character of the first line. In this case, we # end up getting a very large negative number for the line offset. # So, we just assume the user is at the first character. # if lineOffset < 0: caretOffset = 0 lineOffset = 0 texti = acc.queryText() string, startOffset, endOffset = \ texti.getTextAtOffset(0, pyatspi.TEXT_BOUNDARY_LINE_START) return string, caretOffset, lineOffset def getTextEndOffset(self, textInterface): """Returns the offset which should be used as the end offset. By default, this is -1. However, this value triggers an assertion in certain apps. See bug 598797. Argument: - textInterface: the accessible text interface for which the end offset is desired. """ return textInterface.characterCount