[orca/orca-gnome3: 8/87] Converting chat.py into a plugin
- From: Alejandro Leiva <aleiva src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [orca/orca-gnome3: 8/87] Converting chat.py into a plugin
- Date: Fri, 1 Apr 2011 11:13:29 +0000 (UTC)
commit a466958145823940ef2e0136fffb97043d099fae
Author: José Ignacio �lvarez Ruiz <jialvarez emergya es>
Date: Thu Feb 17 15:11:18 2011 +0100
Converting chat.py into a plugin
src/orca/baseplugins/chat.py | 1017 ++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1017 insertions(+), 0 deletions(-)
---
diff --git a/src/orca/baseplugins/chat.py b/src/orca/baseplugins/chat.py
new file mode 100644
index 0000000..71123cc
--- /dev/null
+++ b/src/orca/baseplugins/chat.py
@@ -0,0 +1,1017 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# 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 Joanmarie Diggs."
+#__license__ = "LGPL"
+#
+import pyatspi
+
+import input_event
+import keybindings
+import orca
+import orca_state
+import settings
+#import speech
+#
+from orca_i18n import _
+from pluglib.interfaces import *
+
+#_settingsManager = getattr(orca, '_settingsManager')
+
+class chatPlugin(IPlugin):
+ name = 'Chat plugin'
+ description = 'A chat plugin'
+ version = '0.1pre'
+ authors = ['J. Ignacio Alvarez <neonigma gmail com>']
+ website = 'http://www.nacho-alvarez.es'
+ icon = 'gtk-missing-image'
+
+IPlugin.register(chatPlugin)
+
+#############################################################################
+# #
+# 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 = settings.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,
+ _("Toggle whether we prefix chat room messages with " \
+ "the name of the chat room."))
+
+ self.inputEventHandlers["toggleBuddyTypingHandler"] = \
+ input_event.InputEventHandler(
+ self.toggleBuddyTyping,
+ _("Toggle whether we announce when our buddies are typing."))
+
+ self.inputEventHandlers["toggleMessageHistoriesHandler"] = \
+ input_event.InputEventHandler(
+ self.toggleMessageHistories,
+ _("Toggle whether we provide chat room specific message " \
+ "histories."))
+
+ # Add the chat room message history event handler.
+ #
+ self.inputEventHandlers["reviewMessage"] = \
+ input_event.InputEventHandler(
+ self.readPreviousMessage,
+ _("Speak and braille a previous chat room 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(
+ "",
+ settings.defaultModifierMask,
+ settings.NO_MODIFIER_MASK,
+ self.inputEventHandlers["togglePrefixHandler"]))
+
+ keyBindings.add(
+ keybindings.KeyBinding(
+ "",
+ settings.defaultModifierMask,
+ settings.NO_MODIFIER_MASK,
+ self.inputEventHandlers["toggleBuddyTypingHandler"]))
+
+ keyBindings.add(
+ keybindings.KeyBinding(
+ "",
+ settings.defaultModifierMask,
+ settings.NO_MODIFIER_MASK,
+ self.inputEventHandlers["toggleMessageHistoriesHandler"]))
+
+ for messageKey in self.messageKeys:
+ keyBindings.add(
+ keybindings.KeyBinding(
+ messageKey,
+ self.messageKeyModifier,
+ settings.ORCA_MODIFIER_MASK,
+ self.inputEventHandlers["reviewMessage"]))
+
+ return keyBindings
+
+ def getAppPreferencesGUI(self):
+ """Return a GtkVBox contain the application unique configuration
+ GUI items for the current application.
+ """
+
+ import gtk
+
+ vbox = gtk.VBox(False, 0)
+ vbox.set_border_width(12)
+ gtk.Widget.show(vbox)
+
+ # Translators: If this checkbox is checked, then Orca will speak
+ # the name of the chat room.
+ #
+ label = _("_Speak Chat Room name")
+ self.speakNameCheckButton = gtk.CheckButton(label)
+ gtk.Widget.show(self.speakNameCheckButton)
+ gtk.Box.pack_start(vbox, self.speakNameCheckButton, False, False, 0)
+ gtk.ToggleButton.set_active(
+ self.speakNameCheckButton,
+ _settingsManager.getSetting('chatSpeakRoomName'))
+
+ # Translators: If this checkbox is checked, then Orca will tell
+ # you when one of your buddies is typing a message.
+ #
+ label = _("Announce when your _buddies are typing")
+ self.buddyTypingCheckButton = gtk.CheckButton(label)
+ gtk.Widget.show(self.buddyTypingCheckButton)
+ gtk.Box.pack_start(vbox, self.buddyTypingCheckButton, False, False, 0)
+ gtk.ToggleButton.set_active(
+ self.buddyTypingCheckButton,
+ _settingsManager.getSetting('chatAnnounceBuddyTyping'))
+
+ # Translators: If this checkbox is checked, then Orca will provide
+ # the user with chat room specific message histories rather than just
+ # a single history which contains the latest messages from all the
+ # chat rooms that they are currently in.
+ #
+ label = _("Provide chat room specific _message histories")
+ self.chatRoomHistoriesCheckButton = gtk.CheckButton(label)
+ gtk.Widget.show(self.chatRoomHistoriesCheckButton)
+ gtk.Box.pack_start(vbox, self.chatRoomHistoriesCheckButton,
+ False, False, 0)
+ gtk.ToggleButton.set_active(
+ self.chatRoomHistoriesCheckButton,
+ _settingsManager.getSetting('chatRoomHistories'))
+
+ # "Speak Messages" frame.
+ #
+ messagesFrame = gtk.Frame()
+ gtk.Widget.show(messagesFrame)
+ gtk.Box.pack_start(vbox, messagesFrame, False, False, 5)
+
+ messagesAlignment = gtk.Alignment(0.5, 0.5, 1, 1)
+ gtk.Widget.show(messagesAlignment)
+ gtk.Container.add(messagesFrame, messagesAlignment)
+ gtk.Alignment.set_padding(messagesAlignment, 0, 0, 12, 0)
+
+ messagesVBox = gtk.VBox(False, 0)
+ gtk.Widget.show(messagesVBox)
+ gtk.Container.add(messagesAlignment, messagesVBox)
+
+ verbosity = _settingsManager.getSetting('chatMessageVerbosity')
+
+ # Translators: Orca will speak all new chat messages as they appear
+ # irrespective of whether the chat application currently has focus.
+ # This is the default behaviour.
+ #
+ self.allMessagesRadioButton = gtk.RadioButton(None, _("All cha_nnels"))
+ gtk.Widget.show(self.allMessagesRadioButton)
+ gtk.Box.pack_start(messagesVBox, self.allMessagesRadioButton,
+ False, False, 0)
+ gtk.ToggleButton.set_active(
+ self.allMessagesRadioButton,
+ verbosity == settings.CHAT_SPEAK_ALL)
+
+ # Translators: Orca will speak only new chat messages for the channel
+ # that currently has focus, irrespective of whether the chat
+ # application has focus.
+ #
+ self.focusedChannelRadioButton = gtk.RadioButton(
+ self.allMessagesRadioButton,
+ _("A channel only if its _window is active"))
+ gtk.Widget.show(self.focusedChannelRadioButton)
+ gtk.Box.pack_start(messagesVBox, self.focusedChannelRadioButton,
+ False, False, 0)
+ gtk.ToggleButton.set_active(
+ self.focusedChannelRadioButton,
+ verbosity == settings.CHAT_SPEAK_FOCUSED_CHANNEL)
+
+ # Translators: Orca will speak new chat messages for all channels
+ # only when the chat application has focus.
+ #
+ self.allChannelsRadioButton = gtk.RadioButton(
+ self.allMessagesRadioButton,
+ _("All channels when an_y %s window is active") \
+ % self._script.app.name)
+ gtk.Widget.show(self.allChannelsRadioButton)
+ gtk.Box.pack_start(messagesVBox, self.allChannelsRadioButton,
+ False, False, 0)
+ gtk.ToggleButton.set_active(
+ self.allChannelsRadioButton,
+ verbosity == settings.CHAT_SPEAK_ALL_IF_FOCUSED)
+
+ # Translators: this is the title of a panel holding options for
+ # how messages in this application's chat rooms should be spoken.
+ #
+ messagesLabel = gtk.Label("<b>%s</b>" % _("Speak messages from"))
+ gtk.Widget.show(messagesLabel)
+ gtk.Frame.set_label_widget(messagesFrame, messagesLabel)
+ messagesFrame.set_shadow_type(gtk.SHADOW_NONE)
+ gtk.Label.set_use_markup(messagesLabel, True)
+
+ return vbox
+
+ def setAppPreferences(self, prefs):
+ """Write out the application specific preferences lines and set the
+ new values.
+
+ Arguments:
+ - prefs: file handle for application preferences.
+ """
+
+ prefix = "orca.settings"
+
+ value = self.speakNameCheckButton.get_active()
+ _settingsManager.setSetting('chatSpeakRoomName', value)
+ prefs.writelines("\n")
+ prefs.writelines("%s.chatSpeakRoomName = %s\n" % (prefix, value))
+
+ value = self.buddyTypingCheckButton.get_active()
+ _settingsManager.setSetting('chatAnnounceBuddyTyping', value)
+ prefs.writelines("%s.chatAnnounceBuddyTyping = %s\n" % (prefix, value))
+
+ value = self.chatRoomHistoriesCheckButton.get_active()
+ _settingsManager.setSetting('chatRoomHistories', value)
+ prefs.writelines("%s.chatRoomHistories = %s\n" % (prefix, value))
+
+ value = None
+ option = None
+ if self.allMessagesRadioButton.get_active():
+ value = settings.CHAT_SPEAK_ALL
+ option = ("%s.CHAT_SPEAK_ALL" % prefix)
+ elif self.allChannelsRadioButton.get_active():
+ value = settings.CHAT_SPEAK_ALL_IF_FOCUSED
+ option = ("%s.CHAT_SPEAK_ALL_IF_FOCUSED" % prefix)
+ elif self.focusedChannelRadioButton.get_active():
+ value = settings.CHAT_SPEAK_FOCUSED_CHANNEL
+ option = ("%s.CHAT_SPEAK_FOCUSED_CHANNEL" % prefix)
+
+ if value and option:
+ _settingsManager.setSetting('chatMessageVerbosity', value)
+ prefs.writelines("\n")
+ prefs.writelines("%s.chatMessageVerbosity = %s\n" % \
+ (prefix, option))
+
+ ########################################################################
+ # #
+ # 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 = _("speak chat room name.")
+ speakRoomName = _settingsManager.getSetting('chatSpeakRoomName')
+ _settingsManager.setSetting('chatSpeakRoomName', not speakRoomName)
+ if speakRoomName:
+ line = _("Do not speak chat room name.")
+ 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 = _("announce when your buddies are typing.")
+ announceTyping = _settingsManager.getSetting('chatAnnounceBuddyTyping')
+ _settingsManager.setSetting(
+ 'chatAnnounceBuddyTyping', not announceTyping)
+ if announceTyping:
+ line = _("Do not announce when your buddies are typing.")
+ 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 = _("Provide chat room specific message histories.")
+ roomHistories = _settingsManager.getSetting('chatRoomHistories')
+ _settingsManager.setSetting('chatRoomHistories', not roomHistories)
+ if roomHistories:
+ line = _("Do not provide chat room specific message histories.")
+ 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 = self._script.getSettings().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 _settingsManager.getSetting('chatSpeakRoomName') and chatRoomName:
+ text = _("Message from chat room %s") % chatRoomName
+ text = self._script.utilities.appendString(text, message)
+
+ if len(text.strip()):
+ speech.speak(text)
+ 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
+ if text.decode("UTF-8").isupper():
+ speech.speak(text,
+ self._script.voices[settings.UPPERCASE_VOICE])
+ else:
+ speech.speak(text)
+ 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()):
+ speech.speak(status)
+ 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):
+ topLevel = self._script.utilities.topLevelObject(obj)
+ if topLevel and topLevel.getState().contains(pyatspi.STATE_ACTIVE):
+ return 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
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]