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 script 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.bookmarks as bookmarks
31import orca.scripts.default as default
32import orca.orca as orca
33import orca.settings_manager as settings_manager
34import orca.orca_state as orca_state
35import orca.scripts.toolkits.Gecko as Gecko
36import orca.speech as speech
37
38from .chat import Chat
39from .script_utilities import Utilities
40
41_settingsManager = settings_manager.getManager()
42
43########################################################################
44#                                                                      #
45# The Instantbird script class.                                        #
46#                                                                      #
47########################################################################
48
49class Script(Gecko.Script):
50
51    def __init__(self, app):
52        """Creates a new script for the given application."""
53
54        # So we can take an educated guess at identifying the buddy list.
55        #
56        self._buddyListAncestries = [[pyatspi.ROLE_LIST,
57                                      pyatspi.ROLE_FRAME]]
58
59        Gecko.Script.__init__(self, app)
60
61    def getBookmarks(self):
62        """Returns the "bookmarks" class for this script."""
63
64        # This is a copy of orca.script.getBookmarks(). It's here to
65        # prevent the Gecko script's from being used.
66        #
67        try:
68            return self.bookmarks
69        except AttributeError:
70            self.bookmarks = bookmarks.Bookmarks(self)
71            return self.bookmarks
72
73    def getChat(self):
74        """Returns the 'chat' class for this script."""
75
76        return Chat(self, self._buddyListAncestries)
77
78    def getUtilities(self):
79        """Returns the utilites for this script."""
80
81        return Utilities(self)
82
83    def getEnabledStructuralNavigationTypes(self):
84        """Returns a list of the structural navigation object types
85        enabled in this script.
86        """
87
88        return []
89
90    def setupInputEventHandlers(self):
91        """Defines InputEventHandler fields for this script that can be
92        called by the key and braille bindings. Here we need to add the
93        handlers for chat functionality.
94        """
95
96        Gecko.Script.setupInputEventHandlers(self)
97        self.inputEventHandlers.update(self.chat.inputEventHandlers)
98
99    def getAppKeyBindings(self):
100        """Returns the application-specific keybindings for this script."""
101
102        return self.chat.keyBindings
103
104    def getAppPreferencesGUI(self):
105        """Return a GtkGrid containing the application unique configuration
106        GUI items for the current application. The chat-related options get
107        created by the chat module."""
108
109        return self.chat.getAppPreferencesGUI()
110
111    def getPreferencesFromGUI(self):
112        """Returns a dictionary with the app-specific preferences."""
113
114        return self.chat.getPreferencesFromGUI()
115
116    def onTextDeleted(self, event):
117        """Called whenever text is deleted from an object.
118
119        Arguments:
120        - event: the Event
121        """
122
123        default.Script.onTextDeleted(self, event)
124
125    def onTextInserted(self, event):
126        """Called whenever text is added to an object."""
127
128        if self.chat.presentInsertedText(event):
129            return
130
131        default.Script.onTextInserted(self, event)
132
133    def onCaretMoved(self, event):
134        """Caret movement in Gecko is somewhat unreliable and
135        unpredictable, but we need to handle it.  When we detect caret
136        movement, we make sure we update our own notion of the caret
137        position: our caretContext is an [obj, characterOffset] that
138        points to our current item and character (if applicable) of
139        interest.  If our current item doesn't implement the
140        accessible text specialization, the characterOffset value
141        is meaningless (and typically -1)."""
142
143        if self.utilities.inDocumentContent(event.source):
144            orca.setLocusOfFocus(event, event.source)
145            Gecko.Script.onCaretMoved(self, event)
146        else:
147            default.Script.onCaretMoved(self, event)
148
149    def onChildrenAdded(self, event):
150        """Callback for object:children-changed:add accessibility events."""
151
152        return
153
154    def onDocumentLoadComplete(self, event):
155        """Called when a web page load is completed."""
156
157        return
158
159    def onDocumentLoadStopped(self, event):
160        """Called when a web page load is interrupted."""
161
162        return
163
164    def onNameChanged(self, event):
165        """Called whenever a property on an object changes.
166
167        Arguments:
168        - event: the Event
169        """
170
171        default.Script.onNameChanged(self, event)
172
173    def onFocusedChanged(self, event):
174        """Callback for object:state-changed:focused accessibility events."""
175
176        # This seems to be the most reliable way to identify that the
177        # active chatroom was changed via keyboard from within the entry.
178        # In this case, speak and flash braille the new room name.
179        #
180        if orca_state.locusOfFocus and event.source \
181           and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_ENTRY \
182           and event.source.getRole() == pyatspi.ROLE_ENTRY \
183           and orca_state.locusOfFocus != event.source:
184            room1 = self.chat.getChatRoomName(orca_state.locusOfFocus)
185            room2 = self.chat.getChatRoomName(event.source)
186            if room1 != room2:
187                speech.speak(room2)
188                flashTime = _settingsManager.getSetting('brailleFlashTime')
189                self.displayBrailleMessage(room2, flashTime)
190                orca.setLocusOfFocus(event, event.source)
191                return
192
193        if self.utilities.inDocumentContent(event.source):
194            Gecko.Script.onFocusedChanged(self, event)
195        else:
196            default.Script.onFocusedChanged(self, event)
197
198    def onWindowActivated(self, event):
199        """Called whenever a toplevel window is activated."""
200
201        # Hack to "tickle" the accessible hierarchy. Otherwise, the
202        # events we need to present text added to the chatroom are
203        # missing.
204        hasRole = lambda x: x and x.getRole() == pyatspi.ROLE_PAGE_TAB
205        allPageTabs = pyatspi.findAllDescendants(event.source, hasRole)
206        default.Script.onWindowActivated(self, event)
207