[orca] Work on bgo#615485 - Orca should support the Instantbird chat client



commit f61442d94a3c2516cd6e3830bf03177c8dee7b8e
Author: Joanmarie Diggs <Joanmarie Diggs gmail com>
Date:   Sun Apr 11 23:09:54 2010 -0400

    Work on bgo#615485 - Orca should support the Instantbird chat client
    
    This is just a start. Still need to look at:
    
    1. Better presentation of tree items in buddy list.
    2. Get typing status working.
    3. Get room-specific chat histories working (seems to work ok when
       there's a single history rather than one per room)
    
    Plus testing and user feedback, of course. Note that this assumes
    you are using Instantbird nightly dev/beta (0.2).

 configure.in                                  |    1 +
 src/orca/chat.py                              |   21 +++-
 src/orca/scripts/apps/Instantbird/Makefile.am |    9 ++
 src/orca/scripts/apps/Instantbird/__init__.py |   20 +++
 src/orca/scripts/apps/Instantbird/chat.py     |  169 +++++++++++++++++++++++++
 src/orca/scripts/apps/Instantbird/script.py   |  122 ++++++++++++++++++
 src/orca/scripts/apps/Makefile.am             |    1 +
 7 files changed, 340 insertions(+), 3 deletions(-)
---
diff --git a/configure.in b/configure.in
index f775675..862000f 100644
--- a/configure.in
+++ b/configure.in
@@ -79,6 +79,7 @@ src/orca/scripts/apps/soffice/Makefile
 src/orca/scripts/apps/empathy/Makefile
 src/orca/scripts/apps/evolution/Makefile
 src/orca/scripts/apps/gcalctool/Makefile
+src/orca/scripts/apps/Instantbird/Makefile
 src/orca/scripts/apps/packagemanager/Makefile
 src/orca/scripts/apps/pidgin/Makefile
 src/orca/scripts/apps/planner/Makefile
diff --git a/src/orca/chat.py b/src/orca/chat.py
index abef254..464e0d5 100644
--- a/src/orca/chat.py
+++ b/src/orca/chat.py
@@ -662,6 +662,19 @@ class Chat:
             speech.speak(text)
         braille.displayMessage(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.
@@ -711,8 +724,9 @@ class Chat:
             else:
                 conversation = self.getConversation(event.source)
                 name = conversation.name
-            message = event.any_data.strip("\n")
-            self.addMessageToHistory(message, conversation)
+            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.
@@ -720,7 +734,8 @@ class Chat:
             focused = self.isFocusedChat(event.source)
             if focused:
                 name = ""
-            self.utterMessage(name, event.any_data, focused)
+            if message:
+                self.utterMessage(name, message, focused)
             return True
 
         elif self.isAutoCompletedTextEvent(event):
diff --git a/src/orca/scripts/apps/Instantbird/Makefile.am b/src/orca/scripts/apps/Instantbird/Makefile.am
new file mode 100644
index 0000000..bdbeb1b
--- /dev/null
+++ b/src/orca/scripts/apps/Instantbird/Makefile.am
@@ -0,0 +1,9 @@
+orca_pathdir=$(pyexecdir)
+
+orca_python_PYTHON = \
+	__init__.py \
+	chat.py \
+	script.py
+
+orca_pythondir=$(pyexecdir)/orca/scripts/apps/Instantbird
+
diff --git a/src/orca/scripts/apps/Instantbird/__init__.py b/src/orca/scripts/apps/Instantbird/__init__.py
new file mode 100644
index 0000000..c81df83
--- /dev/null
+++ b/src/orca/scripts/apps/Instantbird/__init__.py
@@ -0,0 +1,20 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 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
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library 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.
+
+from script import Script
diff --git a/src/orca/scripts/apps/Instantbird/chat.py b/src/orca/scripts/apps/Instantbird/chat.py
new file mode 100644
index 0000000..7bd09b5
--- /dev/null
+++ b/src/orca/scripts/apps/Instantbird/chat.py
@@ -0,0 +1,169 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 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
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library 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.
+
+"""Custom chat module for Instantbird."""
+
+__id__        = "$Id$"
+__version__   = "$Revision$"
+__date__      = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__   = "LGPL"
+
+import pyatspi
+
+import orca.chat as chat
+
+########################################################################
+#                                                                      #
+# The Instantbird chat class.                                          #
+#                                                                      #
+########################################################################
+
+class Chat(chat.Chat):
+
+    def __init__(self, script, buddyListAncestries):
+        # IMs get inserted as embedded object characters in these roles.
+        #
+        self._messageParentRoles = [pyatspi.ROLE_DOCUMENT_FRAME,
+                                    pyatspi.ROLE_SECTION]
+
+        chat.Chat.__init__(self, script, buddyListAncestries)
+
+    ########################################################################
+    #                                                                      #
+    # InputEvent handlers and supporting utilities                         #
+    #                                                                      #
+    ########################################################################
+
+    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.)
+        """
+
+        string = ""
+
+        # IMs are written in areas that look like bubbles. When a new bubble
+        # is inserted, we see an embedded object character inserted into the
+        # document frame. The first paragraph is the bubble title; the
+        # rest (usually just one) are the message itself.
+        #
+        if event.source.getRole() == pyatspi.ROLE_DOCUMENT_FRAME:
+            bubble = event.source[event.detail1]
+            paragraphs = self._script.findByRole(bubble, pyatspi.ROLE_PARAGRAPH)
+            for paragraph in paragraphs:
+                try:
+                    msg = paragraph.queryText().getText(0, -1)
+                except:
+                    pass
+                else:
+                    string = self._script.appendString(string, msg)
+
+            return string
+
+        # If we instead have a section, we are writing another message into
+        # the existing bubble. In this case, we get three separate items
+        # inserted: a separator, a paragraph with the desired text, and an
+        # empty section.
+        #
+        if event.source.getRole() == pyatspi.ROLE_SECTION:
+            obj = event.source[event.detail1]
+            if obj and obj.getRole() == pyatspi.ROLE_PARAGRAPH:
+                try:
+                    text = obj.queryText()
+                except:
+                    pass
+                else:
+                    string = text.getText(0, -1)
+
+        return string
+
+    ########################################################################
+    #                                                                      #
+    # Convenience methods for identifying, locating different accessibles  #
+    #                                                                      #
+    ########################################################################
+
+    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.
+        """
+
+        # We might need to refine this later. For now, just get things
+        # working.
+        #
+        if obj and obj.getRole() in self._messageParentRoles:
+            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.
+        """
+
+        ancestor = self._script.getAncestor(obj,
+                                            [pyatspi.ROLE_SCROLL_PANE,
+                                             pyatspi.ROLE_FRAME],
+                                            [pyatspi.ROLE_APPLICATION])
+
+        if ancestor and ancestor.getRole() == pyatspi.ROLE_SCROLL_PANE:
+            # The scroll pane has a proper labelled by relationship set.
+            #
+            return self._script.getDisplayedLabel(ancestor)
+
+        try:
+            text = self._script.getDisplayedText(ancestor)
+            if text.lower().strip() != self._script.name.lower().strip():
+                return text
+        except:
+            return ""
+
+    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.
+        """
+
+        # Normally, we'd see if the top level window associated
+        # with this object had STATE_ACTIVE. That doesn't work
+        # here. So see if the script for the locusOfFocus is
+        # this script. If so, the only other possibility is that
+        # we're in the buddy list instead.
+        #
+        if obj and obj.getState().contains(pyatspi.STATE_SHOWING) \
+           and self._script.isInActiveApp(obj) and not self.isInBuddyList(obj):
+            return True
+
+        return False
diff --git a/src/orca/scripts/apps/Instantbird/script.py b/src/orca/scripts/apps/Instantbird/script.py
new file mode 100644
index 0000000..a20bc26
--- /dev/null
+++ b/src/orca/scripts/apps/Instantbird/script.py
@@ -0,0 +1,122 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 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
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library 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.
+
+"""Custom script for Instantbird."""
+
+__id__        = "$Id$"
+__version__   = "$Revision$"
+__date__      = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__   = "LGPL"
+
+import pyatspi
+
+import orca.default as default
+
+from chat import Chat
+
+########################################################################
+#                                                                      #
+# The Instantbird script class.                                        #
+#                                                                      #
+########################################################################
+
+class Script(default.Script):
+
+    def __init__(self, app):
+        """Creates a new script for the given application."""
+
+        # So we can take an educated guess at identifying the buddy list.
+        #
+        self._buddyListAncestries = [[pyatspi.ROLE_LIST,
+                                      pyatspi.ROLE_FRAME]]
+
+        # We want the functionality of the default script without the
+        # conflicting enhancements we'd pull in from the Gecko script.
+        # (Widgets may be a different story, but for now let's try
+        # subclassing the default script rather than the Gecko script.)
+        #
+        default.Script.__init__(self, app)
+
+    def getChat(self):
+        """Returns the 'chat' class for this script."""
+
+        return Chat(self, self._buddyListAncestries)
+
+    def setupInputEventHandlers(self):
+        """Defines InputEventHandler fields for this script that can be
+        called by the key and braille bindings. Here we need to add the
+        handlers for chat functionality.
+        """
+
+        default.Script.setupInputEventHandlers(self)
+        self.inputEventHandlers.update(self.chat.inputEventHandlers)
+
+    def getKeyBindings(self):
+        """Defines the key bindings for this script. Here we need to add
+        the keybindings associated with chat functionality.
+
+        Returns an instance of keybindings.KeyBindings.
+        """
+
+        keyBindings = default.Script.getKeyBindings(self)
+
+        bindings = self.chat.keyBindings
+        for keyBinding in bindings.keyBindings:
+            keyBindings.add(keyBinding)
+
+        return keyBindings
+
+    def getAppPreferencesGUI(self):
+        """Return a GtkVBox contain the application unique configuration
+        GUI items for the current application. The chat-related options
+        get created by the chat module.
+        """
+
+        return self.chat.getAppPreferencesGUI()
+
+    def setAppPreferences(self, prefs):
+        """Write out the application specific preferences lines and set the
+        new values. The chat-related options get written out by the chat
+        module.
+
+        Arguments:
+        - prefs: file handle for application preferences.
+        """
+
+        self.chat.setAppPreferences(prefs)
+
+    def onTextInserted(self, event):
+        """Called whenever text is added to an object."""
+
+        if self.chat.presentInsertedText(event):
+            return
+
+        default.Script.onTextInserted(self, event)
+
+    def onWindowActivated(self, event):
+        """Called whenever a toplevel window is activated."""
+
+        # Hack to "tickle" the accessible hierarchy. Otherwise, the
+        # events we need to present text added to the chatroom are
+        # missing.
+        #
+        allPageTabs = self.findByRole(event.source, pyatspi.ROLE_PAGE_TAB)
+
+        default.Script.onWindowActivated(self, event)
diff --git a/src/orca/scripts/apps/Makefile.am b/src/orca/scripts/apps/Makefile.am
index 917116b..44a5e87 100644
--- a/src/orca/scripts/apps/Makefile.am
+++ b/src/orca/scripts/apps/Makefile.am
@@ -11,6 +11,7 @@ SUBDIRS = \
 	gnome-window-properties \
 	Banshee \
 	empathy \
+	Instantbird \
 	yelp
 
 orca_pathdir=$(pyexecdir)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]