# Orca
#
# Copyright 2010-2011 The Orca Team
#
# 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.
"""Implements generic chat support."""
__id__ = "$Id$"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2010-2011 The Orca Team"
__license__ = "LGPL"
import pyatspi
from . import cmdnames
from . import debug
from . import guilabels
from . import input_event
from . import keybindings
from . import messages
from . import orca_state
from . import settings
from . import settings_manager
_settingsManager = settings_manager.getManager()
#############################################################################
# #
# Ring List. A fixed size circular list by Flavio Catalani #
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/435902 #
# #
# Included here to keep track of conversation histories. #
# #
#############################################################################
class RingList:
def __init__(self, length):
self.__data__ = []
self.__full__ = 0
self.__max__ = length
self.__cur__ = 0
def append(self, x):
if self.__full__ == 1:
for i in range (0, self.__cur__ - 1):
self.__data__[i] = self.__data__[i + 1]
self.__data__[self.__cur__ - 1] = x
else:
self.__data__.append(x)
self.__cur__ += 1
if self.__cur__ == self.__max__:
self.__full__ = 1
def get(self):
return self.__data__
def remove(self):
if (self.__cur__ > 0):
del self.__data__[self.__cur__ - 1]
self.__cur__ -= 1
def size(self):
return self.__cur__
def maxsize(self):
return self.__max__
def __str__(self):
return ''.join(self.__data__)
#############################################################################
# #
# Conversation #
# #
#############################################################################
class Conversation:
# The number of messages to keep in the history
#
MESSAGE_LIST_LENGTH = 9
def __init__(self, name, accHistory, inputArea=None):
"""Creates a new instance of the Conversation class.
Arguments:
- name: the chatroom/conversation name
- accHistory: the accessible which holds the conversation history
- inputArea: the editable text object for this conversation.
"""
self.name = name
self.accHistory = accHistory
self.inputArea = inputArea
# A cyclic list to hold the chat room history for this conversation
#
self._messageHistory = RingList(Conversation.MESSAGE_LIST_LENGTH)
# Initially populate the cyclic lists with empty strings.
#
i = 0
while i < self._messageHistory.maxsize():
self.addMessage("")
i += 1
# Keep track of the last typing status because some platforms (e.g.
# MSN) seem to issue the status constantly and even though it has
# not changed.
#
self._typingStatus = ""
def addMessage(self, message):
"""Adds the current message to the message history.
Arguments:
- message: A string containing the message to add
"""
self._messageHistory.append(message)
def getNthMessage(self, messageNumber):
"""Returns the specified message from the message history.
Arguments:
- messageNumber: the index of the message to get.
"""
messages = self._messageHistory.get()
return messages[messageNumber]
def getTypingStatus(self):
"""Returns the typing status of the buddy in this conversation."""
return self._typingStatus
def setTypingStatus(self, status):
"""Sets the typing status of the buddy in this conversation.
Arguments:
- status: a string describing the current status.
"""
self._typingStatus = status
#############################################################################
# #
# ConversationList #
# #
#############################################################################
class ConversationList:
def __init__(self, messageListLength):
"""Creates a new instance of the ConversationList class.
Arguments:
- messageListLength: the size of the message history to keep.
"""
self.conversations = []
# A cyclic list to hold the most recent (messageListLength) previous
# messages for all conversations in the ConversationList.
#
self._messageHistory = RingList(messageListLength)
# A corresponding cyclic list to hold the name of the conversation
# associated with each message in the messageHistory.
#
self._roomHistory = RingList(messageListLength)
# Initially populate the cyclic lists with empty strings.
#
i = 0
while i < self._messageHistory.maxsize():
self.addMessage("", None)
i += 1
def addMessage(self, message, conversation):
"""Adds the current message to the message history.
Arguments:
- message: A string containing the message to add
- conversation: The instance of the Conversation class with which
the message is associated
"""
if not conversation:
name = ""
else:
if not self.hasConversation(conversation):
self.addConversation(conversation)
name = conversation.name
self._messageHistory.append(message)
self._roomHistory.append(name)
def getNthMessageAndName(self, messageNumber):
"""Returns a list containing the specified message from the message
history and the name of the chatroom/conversation associated with
that message.
Arguments:
- messageNumber: the index of the message to get.
"""
messages = self._messageHistory.get()
rooms = self._roomHistory.get()
return messages[messageNumber], rooms[messageNumber]
def hasConversation(self, conversation):
"""Returns True if we know about this conversation.
Arguments:
- conversation: the conversation of interest
"""
return conversation in self.conversations
def getNConversations(self):
"""Returns the number of conversations we currently know about."""
return len(self.conversations)
def addConversation(self, conversation):
"""Adds conversation to the list of conversations.
Arguments:
- conversation: the conversation to add
"""
self.conversations.append(conversation)
def removeConversation(self, conversation):
"""Removes conversation from the list of conversations.
Arguments:
- conversation: the conversation to remove
Returns True if conversation was successfully removed.
"""
# TODO - JD: In the Pidgin script, I do not believe we handle the
# case where a conversation window is closed. I *think* it remains
# in the overall chat history. What do we want to do in that case?
# I would assume that we'd want to remove it.... So here's a method
# to do so. Nothing in the Chat class uses it yet.
#
try:
self.conversations.remove(conversation)
except:
return False
else:
return True
#############################################################################
# #
# Chat #
# #
#############################################################################
class Chat:
"""This class implements the chat functionality which is available to
scripts.
"""
def __init__(self, script, buddyListAncestries):
"""Creates an instance of the Chat class.
Arguments:
- script: the script with which this instance is associated.
- buddyListAncestries: a list of lists of pyatspi roles beginning
with the the object serving as the actual buddy list (e.g.
ROLE_TREE_TABLE) and ending with the top level object (e.g.
ROLE_FRAME).
"""
self._script = script
self._buddyListAncestries = buddyListAncestries
# Keybindings to provide conversation message history. The message
# review order will be based on the index within the list. Thus F1
# is associated with the most recent message, F2 the message before
# that, and so on. A script could override this. Setting messageKeys
# to ["a", "b", "c" ... ] will cause "a" to be associated with the
# most recent message, "b" to be associated with the message before
# that, etc. Scripts can also override the messageKeyModifier.
#
self.messageKeys = \
["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"]
self.messageKeyModifier = keybindings.ORCA_MODIFIER_MASK
self.inputEventHandlers = {}
self.setupInputEventHandlers()
self.keyBindings = self.getKeyBindings()
# The length of the message history will be based on how many keys
# are bound to the task of providing it.
#
self.messageListLength = len(self.messageKeys)
self._conversationList = ConversationList(self.messageListLength)
# To make pylint happy.
#
self.focusedChannelRadioButton = None
self.allChannelsRadioButton = None
self.allMessagesRadioButton = None
self.buddyTypingCheckButton = None
self.chatRoomHistoriesCheckButton = None
self.speakNameCheckButton = None
def setupInputEventHandlers(self):
"""Defines InputEventHandler fields for chat functions which
will be used by the script associated with this chat instance."""
self.inputEventHandlers["togglePrefixHandler"] = \
input_event.InputEventHandler(
self.togglePrefix,
cmdnames.CHAT_TOGGLE_ROOM_NAME_PREFIX)
self.inputEventHandlers["toggleBuddyTypingHandler"] = \
input_event.InputEventHandler(
self.toggleBuddyTyping,
cmdnames.CHAT_TOGGLE_BUDDY_TYPING)
self.inputEventHandlers["toggleMessageHistoriesHandler"] = \
input_event.InputEventHandler(
self.toggleMessageHistories,
cmdnames.CHAT_TOGGLE_MESSAGE_HISTORIES)
self.inputEventHandlers["reviewMessage"] = \
input_event.InputEventHandler(
self.readPreviousMessage,
cmdnames.CHAT_PREVIOUS_MESSAGE)
return
def getKeyBindings(self):
"""Defines the chat-related key bindings which will be used by
the script associated with this chat instance.
Returns: an instance of keybindings.KeyBindings.
"""
keyBindings = keybindings.KeyBindings()
keyBindings.add(
keybindings.KeyBinding(
"",
keybindings.defaultModifierMask,
keybindings.NO_MODIFIER_MASK,
self.inputEventHandlers["togglePrefixHandler"]))
keyBindings.add(
keybindings.KeyBinding(
"",
keybindings.defaultModifierMask,
keybindings.NO_MODIFIER_MASK,
self.inputEventHandlers["toggleBuddyTypingHandler"]))
keyBindings.add(
keybindings.KeyBinding(
"",
keybindings.defaultModifierMask,
keybindings.NO_MODIFIER_MASK,
self.inputEventHandlers["toggleMessageHistoriesHandler"]))
for messageKey in self.messageKeys:
keyBindings.add(
keybindings.KeyBinding(
messageKey,
self.messageKeyModifier,
keybindings.ORCA_MODIFIER_MASK,
self.inputEventHandlers["reviewMessage"]))
return keyBindings
def getAppPreferencesGUI(self):
"""Return a GtkGrid containing the application unique configuration
GUI items for the current application. """
from gi.repository import Gtk
grid = Gtk.Grid()
grid.set_border_width(12)
label = guilabels.CHAT_SPEAK_ROOM_NAME
value = _settingsManager.getSetting('chatSpeakRoomName')
self.speakNameCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
self.speakNameCheckButton.set_active(value)
grid.attach(self.speakNameCheckButton, 0, 0, 1, 1)
label = guilabels.CHAT_ANNOUNCE_BUDDY_TYPING
value = _settingsManager.getSetting('chatAnnounceBuddyTyping')
self.buddyTypingCheckButton = Gtk.CheckButton.new_with_mnemonic(label)
self.buddyTypingCheckButton.set_active(value)
grid.attach(self.buddyTypingCheckButton, 0, 1, 1, 1)
label = guilabels.CHAT_SEPARATE_MESSAGE_HISTORIES
value = _settingsManager.getSetting('chatRoomHistories')
self.chatRoomHistoriesCheckButton = \
Gtk.CheckButton.new_with_mnemonic(label)
self.chatRoomHistoriesCheckButton.set_active(value)
grid.attach(self.chatRoomHistoriesCheckButton, 0, 2, 1, 1)
messagesFrame = Gtk.Frame()
grid.attach(messagesFrame, 0, 3, 1, 1)
label = Gtk.Label("<b>%s</b>" % guilabels.CHAT_SPEAK_MESSAGES_FROM)
label.set_use_markup(True)
messagesFrame.set_label_widget(label)
messagesAlignment = Gtk.Alignment.new(0.5, 0.5, 1, 1)
messagesAlignment.set_padding(0, 0, 12, 0)
messagesFrame.add(messagesAlignment)
messagesGrid = Gtk.Grid()
messagesAlignment.add(messagesGrid)
value = _settingsManager.getSetting('chatMessageVerbosity')
label = guilabels.CHAT_SPEAK_MESSAGES_ALL
rb1 = Gtk.RadioButton.new_with_mnemonic(None, label)
rb1.set_active(value == settings.CHAT_SPEAK_ALL)
self.allMessagesRadioButton = rb1
messagesGrid.attach(self.allMessagesRadioButton, 0, 0, 1, 1)
label = guilabels.CHAT_SPEAK_MESSAGES_ACTIVE
rb2 = Gtk.RadioButton.new_with_mnemonic(None, label)
rb2.join_group(rb1)
rb2.set_active(value == settings.CHAT_SPEAK_FOCUSED_CHANNEL)
self.focusedChannelRadioButton = rb2
messagesGrid.attach(self.focusedChannelRadioButton, 0, 1, 1, 1)
label = guilabels.CHAT_SPEAK_MESSAGES_ALL_IF_FOCUSED % \
self._script.app.name
rb3 = Gtk.RadioButton.new_with_mnemonic(None, label)
rb3.join_group(rb1)
rb3.set_active(value == settings.CHAT_SPEAK_ALL_IF_FOCUSED)
self.allChannelsRadioButton = rb3
messagesGrid.attach(self.allChannelsRadioButton, 0, 2, 1, 1)
grid.show_all()
return grid
def getPreferencesFromGUI(self):
"""Returns a dictionary with the app-specific preferences."""
if self.allChannelsRadioButton.get_active():
verbosity = settings.CHAT_SPEAK_ALL_IF_FOCUSED
elif self.focusedChannelRadioButton.get_active():
verbosity = settings.CHAT_SPEAK_FOCUSED_CHANNEL
else:
verbosity = settings.CHAT_SPEAK_ALL
return {
'chatMessageVerbosity': verbosity,
'chatSpeakRoomName': self.speakNameCheckButton.get_active(),
'chatAnnounceBuddyTyping': self.buddyTypingCheckButton.get_active(),
'chatRoomHistories': self.chatRoomHistoriesCheckButton.get_active(),
}
########################################################################
# #
# InputEvent handlers and supporting utilities #
# #
########################################################################
def togglePrefix(self, script, inputEvent):
""" Toggle whether we prefix chat room messages with the name of
the chat room.
Arguments:
- script: the script associated with this event
- inputEvent: if not None, the input event that caused this action.
"""
line = messages.CHAT_ROOM_NAME_PREFIX_ON
speakRoomName = _settingsManager.getSetting('chatSpeakRoomName')
_settingsManager.setSetting('chatSpeakRoomName', not speakRoomName)
if speakRoomName:
line = messages.CHAT_ROOM_NAME_PREFIX_OFF
self._script.presentMessage(line)
return True
def toggleBuddyTyping(self, script, inputEvent):
""" Toggle whether we announce when our buddies are typing a message.
Arguments:
- script: the script associated with this event
- inputEvent: if not None, the input event that caused this action.
"""
line = messages.CHAT_BUDDY_TYPING_ON
announceTyping = _settingsManager.getSetting('chatAnnounceBuddyTyping')
_settingsManager.setSetting(
'chatAnnounceBuddyTyping', not announceTyping)
if announceTyping:
line = messages.CHAT_BUDDY_TYPING_OFF
self._script.presentMessage(line)
return True
def toggleMessageHistories(self, script, inputEvent):
""" Toggle whether we provide chat room specific message histories.
Arguments:
- script: the script associated with this event
- inputEvent: if not None, the input event that caused this action.
"""
line = messages.CHAT_SEPARATE_HISTORIES_ON
roomHistories = _settingsManager.getSetting('chatRoomHistories')
_settingsManager.setSetting('chatRoomHistories', not roomHistories)
if roomHistories:
line = messages.CHAT_SEPARATE_HISTORIES_OFF
self._script.presentMessage(line)
return True
def readPreviousMessage(self, script, inputEvent=None, index=0):
""" Speak/braille a previous chat room message.
Arguments:
- script: the script associated with this event
- inputEvent: if not None, the input event that caused this action.
- index: The index of the message to read -- by default, the most
recent message. If we get an inputEvent, however, the value of
index is ignored and the index of the event_string with respect
to self.messageKeys is used instead.
"""
try:
index = self.messageKeys.index(inputEvent.event_string)
except:
pass
messageNumber = self.messageListLength - (index + 1)
message, chatRoomName = None, None
if _settingsManager.getSetting('chatRoomHistories'):
conversation = self.getConversation(orca_state.locusOfFocus)
if conversation:
message = conversation.getNthMessage(messageNumber)
chatRoomName = conversation.name
else:
message, chatRoomName = \
self._conversationList.getNthMessageAndName(messageNumber)
if message and chatRoomName:
self.utterMessage(chatRoomName, message, True)
def utterMessage(self, chatRoomName, message, focused=True):
""" Speak/braille a chat room message.
Arguments:
- chatRoomName: name of the chat room this message came from
- message: the chat room message
- focused: whether or not the current chatroom has focus. Defaults
to True so that we can use this method to present chat history
as well as incoming messages.
"""
# Only speak/braille the new message if it matches how the user
# wants chat messages spoken.
#
verbosity = _settingsManager.getAppSetting(self._script.app, 'chatMessageVerbosity')
if orca_state.activeScript.name != self._script.name \
and verbosity == settings.CHAT_SPEAK_ALL_IF_FOCUSED:
return
elif not focused and verbosity == settings.CHAT_SPEAK_FOCUSED_CHANNEL:
return
text = ""
if chatRoomName and \
_settingsManager.getAppSetting(self._script.app, 'chatSpeakRoomName'):
text = messages.CHAT_MESSAGE_FROM_ROOM % chatRoomName
if not settings.presentChatRoomLast:
text = self._script.utilities.appendString(text, message)
else:
text = self._script.utilities.appendString(message, text)
if len(text.strip()):
voice = self._script.speechGenerator.voice(string=text)
self._script.speakMessage(text, voice=voice)
self._script.displayBrailleMessage(text)
def getMessageFromEvent(self, event):
"""Get the actual displayed message. This will almost always be the
unaltered any_data from an event of type object:text-changed:insert.
Arguments:
- event: the Event from which to take the text.
Returns the string which should be presented as the newly-inserted
text. (Things like chatroom name prefacing get handled elsewhere.)
"""
return event.any_data
def presentInsertedText(self, event):
"""Gives the Chat class an opportunity to present the text from the
text inserted Event.
Arguments:
- event: the text inserted Event
Returns True if we handled this event here; otherwise False, which
tells the associated script that is not a chat event that requires
custom handling.
"""
if not event \
or not event.type.startswith("object:text-changed:insert") \
or not event.any_data:
return False
if self.isGenericTextObject(event.source):
# The script should handle non-chat specific text areas (e.g.,
# adding a new account).
#
return False
elif self.isInBuddyList(event.source):
# These are status changes. What the Pidgin script currently
# does for these is ignore them. It might be nice to add
# some options to allow the user to customize what status
# changes are presented. But for now, we'll ignore them
# across the board.
#
return True
elif self.isTypingStatusChangedEvent(event):
self.presentTypingStatusChange(event, event.any_data)
return True
elif self.isChatRoomMsg(event.source):
# We always automatically go back to focus tracking mode when
# someone sends us a message.
#
if self._script.flatReviewContext:
self._script.toggleFlatReviewMode()
if self.isNewConversation(event.source):
name = self.getChatRoomName(event.source)
conversation = Conversation(name, event.source)
else:
conversation = self.getConversation(event.source)
name = conversation.name
message = self.getMessageFromEvent(event).strip("\n")
if message:
self.addMessageToHistory(message, conversation)
# The user may or may not want us to present this message. Also,
# don't speak the name if it's the focused chat.
#
focused = self.isFocusedChat(event.source)
if focused:
name = ""
if message:
self.utterMessage(name, message, focused)
return True
elif self.isAutoCompletedTextEvent(event):
text = event.any_data
voice = self._script.speechGenerator.voice(string=text)
self._script.speakMessage(text, voice=voice)
return True
return False
def presentTypingStatusChange(self, event, status):
"""Presents a change in typing status for the current conversation
if the status has indeed changed and if the user wants to hear it.
Arguments:
- event: the accessible Event
- status: a string containing the status change
Returns True if we spoke the change; False otherwise
"""
if _settingsManager.getSetting('chatAnnounceBuddyTyping'):
conversation = self.getConversation(event.source)
if conversation and (status != conversation.getTypingStatus()):
voice = self._script.speechGenerator.voice(string=status)
self._script.speakMessage(status, voice=voice)
conversation.setTypingStatus(status)
return True
return False
def addMessageToHistory(self, message, conversation):
"""Adds message to both the individual conversation's history
as well as to the complete history stored in our conversation
list.
Arguments:
- message: a string containing the message to be added
- conversation: the instance of the Conversation class to which
this message belongs
"""
conversation.addMessage(message)
self._conversationList.addMessage(message, conversation)
########################################################################
# #
# Convenience methods for identifying, locating different accessibles #
# #
########################################################################
def isGenericTextObject(self, obj):
"""Returns True if the given accessible seems to be something
unrelated to the custom handling we're attempting to do here.
Arguments:
- obj: the accessible object to examine.
"""
state = obj.getState()
if state.contains(pyatspi.STATE_EDITABLE) \
and state.contains(pyatspi.STATE_SINGLE_LINE):
return True
return False
def isBuddyList(self, obj):
"""Returns True if obj is the list of buddies in the buddy list
window. Note that this method relies upon a hierarchical check,
using a list of hierarchies provided by the script. Scripts
which have more reliable means of identifying the buddy list
can override this method.
Arguments:
- obj: the accessible being examined
"""
if obj:
for roleList in self._buddyListAncestries:
if self._script.utilities.hasMatchingHierarchy(obj, roleList):
return True
return False
def isInBuddyList(self, obj, includeList=True):
"""Returns True if obj is, or is inside of, the buddy list.
Arguments:
- obj: the accessible being examined
- includeList: whether or not the list itself should be
considered "in" the buddy list.
"""
if includeList and self.isBuddyList(obj):
return True
for roleList in self._buddyListAncestries:
buddyListRole = roleList[0]
candidate = self._script.utilities.ancestorWithRole(
obj, [buddyListRole], [pyatspi.ROLE_FRAME])
if self.isBuddyList(candidate):
return True
return False
def isNewConversation(self, obj):
"""Returns True if the given accessible is the chat history
associated with a new conversation.
Arguments:
- obj: the accessible object to examine.
"""
conversation = self.getConversation(obj)
return not self._conversationList.hasConversation(conversation)
def getConversation(self, obj):
"""Attempts to locate the conversation associated with obj.
Arguments:
- obj: the accessible of interest
Returns the conversation if found; None otherwise
"""
if not obj:
return None
name = ""
# TODO - JD: If we have multiple chats going on and those
# chats have the same name, and we're in the input area,
# this approach will fail. What I should probably do instead
# is, upon creation of a new conversation, figure out where
# the input area is and save it. For now, I just want to get
# things working. And people should not be in multiple chat
# rooms with identical names anyway. :-)
#
if obj.getRole() in [pyatspi.ROLE_TEXT, pyatspi.ROLE_ENTRY] \
and obj.getState().contains(pyatspi.STATE_EDITABLE):
name = self.getChatRoomName(obj)
for conversation in self._conversationList.conversations:
if name:
if name == conversation.name:
return conversation
# Doing an equality check seems to be preferable here to
# utilities.isSameObject as a result of false positives.
#
elif obj == conversation.accHistory:
return conversation
return None
def isChatRoomMsg(self, obj):
"""Returns True if the given accessible is the text object for
associated with a chat room conversation.
Arguments:
- obj: the accessible object to examine.
"""
if obj and obj.getRole() == pyatspi.ROLE_TEXT \
and obj.parent.getRole() == pyatspi.ROLE_SCROLL_PANE:
state = obj.getState()
if not state.contains(pyatspi.STATE_EDITABLE) \
and state.contains(pyatspi.STATE_MULTI_LINE):
return True
return False
def isFocusedChat(self, obj):
"""Returns True if we plan to treat this chat as focused for
the purpose of deciding whether or not a message should be
presented to the user.
Arguments:
- obj: the accessible object to examine.
"""
if obj and obj.getState().contains(pyatspi.STATE_SHOWING):
active = self._script.utilities.topLevelObjectIsActiveAndCurrent(obj)
msg = "INFO: %s's window is focused chat: %s" % (obj, active)
debug.println(debug.LEVEL_INFO, msg, True)
return active
msg = "INFO: %s is not focused chat (not showing)" % obj
debug.println(debug.LEVEL_INFO, msg, True)
return False
def getChatRoomName(self, obj):
"""Attempts to find the name of the current chat room.
Arguments:
- obj: The accessible of interest
Returns a string containing what we think is the chat room name.
"""
# Most of the time, it seems that the name can be found in the
# page tab which is the ancestor of the chat history. Failing
# that, we'll look at the frame name. Failing that, scripts
# should override this method. :-)
#
ancestor = self._script.utilities.ancestorWithRole(
obj,
[pyatspi.ROLE_PAGE_TAB, pyatspi.ROLE_FRAME],
[pyatspi.ROLE_APPLICATION])
name = ""
try:
text = self._script.utilities.displayedText(ancestor)
if text.lower().strip() != self._script.name.lower().strip():
name = text
except:
pass
# Some applications don't trash their page tab list when there is
# only one active chat, but instead they remove the text or hide
# the item. Therefore, we'll give it one more shot.
#
if not name:
ancestor = self._script.utilities.ancestorWithRole(
ancestor, [pyatspi.ROLE_FRAME], [pyatspi.ROLE_APPLICATION])
try:
text = self._script.utilities.displayedText(ancestor)
if text.lower().strip() != self._script.name.lower().strip():
name = text
except:
pass
return name
def isAutoCompletedTextEvent(self, event):
"""Returns True if event is associated with text being autocompleted.
Arguments:
- event: the accessible event being examined
"""
lastKey, mods = self._script.utilities.lastKeyAndModifiers()
if lastKey == "Tab" and event.any_data and event.any_data != "\t":
return True
return False
def isTypingStatusChangedEvent(self, event):
"""Returns True if event is associated with a change in typing status.
Arguments:
- event: the accessible event being examined
"""
# TODO - JD: I still need to figure this one out. Pidgin seems to
# no longer be presenting this change in the conversation history
# as it was doing before. And I'm not yet sure what other apps do.
# In the meantime, scripts can override this.
#
return False