1# Orca 2# 3# Copyright 2010 Joanmarie Diggs. 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the 17# Free Software Foundation, Inc., Franklin Street, Fifth Floor, 18# Boston MA 02110-1301 USA. 19 20"""Custom chat module for Instantbird.""" 21 22__id__ = "$Id$" 23__version__ = "$Revision$" 24__date__ = "$Date$" 25__copyright__ = "Copyright (c) 2010 Joanmarie Diggs." 26__license__ = "LGPL" 27 28import pyatspi 29 30import orca.chat as chat 31 32######################################################################## 33# # 34# The Instantbird chat class. # 35# # 36######################################################################## 37 38class Chat(chat.Chat): 39 40 def __init__(self, script, buddyListAncestries): 41 chat.Chat.__init__(self, script, buddyListAncestries) 42 43 ######################################################################## 44 # # 45 # InputEvent handlers and supporting utilities # 46 # # 47 ######################################################################## 48 49 def getMessageFromEvent(self, event): 50 """Get the actual displayed message. This will almost always be the 51 unaltered any_data from an event of type object:text-changed:insert. 52 53 Arguments: 54 - event: the Event from which to take the text. 55 56 Returns the string which should be presented as the newly-inserted 57 text. (Things like chatroom name prefacing get handled elsewhere.) 58 """ 59 60 string = "" 61 62 # IMs are written in areas that look like bubbles. When a new bubble 63 # is inserted, we see an embedded object character inserted into the 64 # document frame. The first paragraph is the bubble title; the 65 # rest (usually just one) are the message itself. 66 # 67 if self._script.utilities.isDocument(event.source): 68 bubble = event.source[event.detail1] 69 hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_PARAGRAPH 70 paragraphs = pyatspi.findAllDescendants(bubble, hasRole) 71 72 # If the user opted the non-default, "simple" appearance, then this 73 # might not be a bubble at all, but a paragraph. 74 # 75 if not paragraphs and bubble.getRole() == pyatspi.ROLE_PARAGRAPH: 76 paragraphs.append(bubble) 77 78 for paragraph in paragraphs: 79 msg = self._script.utilities.substring(paragraph, 0, -1) 80 if msg == self._script.EMBEDDED_OBJECT_CHARACTER: 81 # This seems to occur for non-focused conversations. 82 # 83 msg = self._script.utilities.substring(paragraph[0], 0, -1) 84 string = self._script.utilities.appendString(string, msg) 85 86 return string 87 88 # If we instead have a section, we are writing another message into 89 # the existing bubble. In this case, we get three separate items 90 # inserted: a separator, a paragraph with the desired text, and an 91 # empty section. 92 # 93 if event.source.getRole() == pyatspi.ROLE_SECTION: 94 obj = event.source[event.detail1] 95 if obj and obj.getRole() == pyatspi.ROLE_PARAGRAPH: 96 try: 97 text = obj.queryText() 98 except: 99 pass 100 else: 101 string = text.getText(0, -1) 102 103 return string 104 105 ######################################################################## 106 # # 107 # Convenience methods for identifying, locating different accessibles # 108 # # 109 ######################################################################## 110 111 def isChatRoomMsg(self, obj): 112 """Returns True if the given accessible is the text object for 113 associated with a chat room conversation. 114 115 Arguments: 116 - obj: the accessible object to examine. 117 """ 118 119 if not obj: 120 return False 121 122 if self._script.utilities.isDocument(obj): 123 return True 124 125 return obj.getRole() in [pyatspi.ROLE_SECTION, pyatspi.ROLE_PARAGRAPH] 126 127 def getChatRoomName(self, obj): 128 """Attempts to find the name of the current chat room. 129 130 Arguments: 131 - obj: The accessible of interest 132 133 Returns a string containing what we think is the chat room name. 134 """ 135 136 name = "" 137 ancestor = self._script.utilities.ancestorWithRole( 138 obj, 139 [pyatspi.ROLE_SCROLL_PANE, pyatspi.ROLE_FRAME], 140 [pyatspi.ROLE_APPLICATION]) 141 142 if ancestor and ancestor.getRole() == pyatspi.ROLE_SCROLL_PANE: 143 # The scroll pane has a proper labelled by relationship set. 144 # 145 name = self._script.utilities.displayedLabel(ancestor) 146 147 if not name: 148 try: 149 text = self._script.utilities.displayedText(ancestor) 150 if text.lower().strip() != self._script.name.lower().strip(): 151 name = text 152 except: 153 pass 154 155 return name 156 157 def isFocusedChat(self, obj): 158 """Returns True if we plan to treat this chat as focused for 159 the purpose of deciding whether or not a message should be 160 presented to the user. 161 162 Arguments: 163 - obj: the accessible object to examine. 164 """ 165 166 # Normally, we'd see if the top level window associated 167 # with this object had STATE_ACTIVE. That doesn't work 168 # here. So see if the script for the locusOfFocus is 169 # this script. If so, the only other possibility is that 170 # we're in the buddy list instead. 171 # 172 if obj and obj.getState().contains(pyatspi.STATE_SHOWING) \ 173 and self._script.utilities.isInActiveApp(obj) \ 174 and not self.isInBuddyList(obj): 175 return True 176 177 return False 178