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