Coverage Report - orca.scripts.gedit

ModuleCoverage %
orca.scripts.gedit
60%
1
# Orca
2
#
3
# Copyright 2004-2006 Sun Microsystems Inc.
4
#
5
# This library is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU Library General Public
7
# License as published by the Free Software Foundation; either
8
# version 2 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
# Library General Public License for more details.
14
#
15
# You should have received a copy of the GNU Library General Public
16
# License along with this library; if not, write to the
17
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18
# Boston, MA 02111-1307, USA.
19
20 1
"""Custom script for gedit."""
21
22 1
__id__        = "$Id: gedit.py 2126 2007-03-06 21:35:17Z richb $"
23 1
__version__   = "$Revision: 2126 $"
24 1
__date__      = "$Date: 2007-03-06 13:35:17 -0800 (Tue, 06 Mar 2007) $"
25 1
__copyright__ = "Copyright (c) 2005-2006 Sun Microsystems Inc."
26 1
__license__   = "LGPL"
27
28 1
import orca.debug as debug
29 1
import orca.atspi as atspi
30 1
import orca.braille as braille
31 1
import orca.default as default
32 1
import orca.orca as orca
33 1
import orca.orca_state as orca_state
34 1
import orca.rolenames as rolenames
35 1
import orca.speech as speech
36 1
import orca.speechgenerator as speechgenerator
37
38 1
from orca.orca_i18n import _
39
40
########################################################################
41
#                                                                      #
42
# The GEdit script class.                                              #
43
#                                                                      #
44
########################################################################
45
46 2
class SpeechGenerator(speechgenerator.SpeechGenerator):
47
    """Overrides _getSpeechForFrame so as to avoid digging into the
48
    gedit hierarchy and tickling a bug in gedit.
49
    """
50 1
    def __init__(self, script):
51 9
        speechgenerator.SpeechGenerator.__init__(self, script)
52
53 1
    def _getSpeechForFrame(self, obj, already_focused):
54
        """Get the speech for a frame.  [[[TODO: WDW - This avoids
55
        digging into the component hierarchy so as to avoid tickling
56
        a bug in GEdit (see module comment above).]]]
57
58
        Arguments:
59
        - obj: the frame
60
        - already_focused: if False, the obj just received focus
61
62
        Returns a list of utterances to be spoken for the object.
63
        """
64
65 7
        utterances = self._getDefaultSpeech(obj, already_focused)
66
67
        # This will dig deep into the hierarchy, causing issues with
68
        # gedit.  So, we won't do this.
69
        #
70
        #utterances = self._getSpeechForAlert(obj, already_focused)
71
72 7
        self._debugGenerator("GEditSpeechGenerator._getSpeechForFrame",
73 7
                             obj,
74 7
                             already_focused,
75 7
                             utterances)
76
77 7
        return utterances
78
79 2
class Script(default.Script):
80
81 1
    def __init__(self, app):
82
        """Creates a new script for the given application.
83
84
        Arguments:
85
        - app: the application to create a script for.
86
        """
87
88 9
        default.Script.__init__(self, app)
89
90
        # Set the debug level for all the methods in this script.
91
        #
92 9
        self.debugLevel = debug.LEVEL_FINEST
93
94
        # This will be used to cache a handle to the gedit text area for
95
        # spell checking purposes.
96
97 9
        self.textArea = None
98
99
        # The following variables will be used to try to determine if we've
100
        # already handled this misspelt word (see readMisspeltWord() for
101
        # more details.
102
103 9
        self.lastCaretPosition = -1
104 9
        self.lastBadWord = ''
105 9
        self.lastEventType = ''
106
107 1
    def getSpeechGenerator(self):
108
        """Returns the speech generator for this script.
109
        """
110 9
        return SpeechGenerator(self)
111
112 1
    def readMisspeltWord(self, event, panel):
113
        """Speak/braille the current misspelt word plus its context.
114
           The spell check dialog contains a "paragraph" which shows the
115
           context for the current spelling mistake. After speaking/brailling
116
           the default action for this component, that a selection of the
117
           surronding text from that paragraph with the misspelt word is also
118
           spoken.
119
120
        Arguments:
121
        - event: the event.
122
        - panel: the panel in the check spelling dialog containing the label
123
                 with the misspelt word.
124
        """
125
126
        # Braille the default action for this component.
127
        #
128 0
        self.updateBraille(orca_state.locusOfFocus)
129
130
        # Look for the label containing the misspelled word.
131
        # There will be three labels in the top panel in the Check
132
        # Spelling dialog. Look for the one that isn't a label to
133
        # another component.
134
        #
135 0
        allLabels = self.findByRole(panel, rolenames.ROLE_LABEL)
136 0
        for i in range(0, len(allLabels)):
137 0
            if allLabels[i].name.startswith(_("Change to:")) or \
138
               allLabels[i].name.startswith(_("Misspelled word:")):
139 0
                continue
140
            else:
141 0
                badWord = allLabels[i].name
142 0
                break
143
144
        # Note that we often get two or more of these focus or property-change
145
        # events each time there is a new misspelt word. We extract the
146
        # current text caret position and the misspelt word and compare
147
        # them against the values saved from the last time this routine
148
        # was called. If they are the same then we ignore it.
149
150 0
        if self.textArea != None:
151 0
            allText = self.findByRole(self.textArea, rolenames.ROLE_TEXT)
152 0
            caretPosition = allText[0].text.caretOffset
153
154 0
            debug.println(self.debugLevel, \
155 0
                "gedit.readMisspeltWord: type=%s  word=%s caret position=%d" \
156
                % (event.type, badWord, caretPosition))
157
158 0
            if (caretPosition == self.lastCaretPosition) and \
159
               (badWord == self.lastBadWord) and \
160
               (event.type == self.lastEventType):
161 0
                return
162
163
            # The indication that spell checking is complete is when the
164
            # "misspelt" word is set to "Completed spell checking". Ugh!
165
            # Try to detect this and let the user know.
166
167 0
            if badWord == _("Completed spell checking"):
168 0
                utterance = _("Spell checking is complete.")
169 0
                speech.speak(utterance)
170 0
                utterance = _("Press Tab and Return to terminate.")
171 0
                speech.speak(utterance)
172 0
                return
173
174
            # If we have a handle to the gedit text area, then extract out
175
            # all the text objects, and create a list of all the words found
176
            # in them.
177
            #
178 0
            allTokens = []
179 0
            for i in range(0, len(allText)):
180 0
                text = self.getText(allText[i], 0, -1)
181 0
                tokens = text.split()
182 0
                allTokens += tokens
183
184 0
            self.speakMisspeltWord(allTokens, badWord)
185
186
            # Save misspelt word information for comparison purposes
187
            # next time around.
188
            #
189 0
            self.lastCaretPosition = caretPosition
190 0
            self.lastBadWord = badWord
191 0
            self.lastEventType = event.type
192
193 1
    def isFocusOnFindDialog(self):
194
        """Return an indication of whether the current locus of focus is on
195
        the Find button or the combo box in the Find dialog.
196
        """
197
198 776
        obj = orca_state.locusOfFocus
199 776
        rolesList1 = [rolenames.ROLE_PUSH_BUTTON,
200
                     rolenames.ROLE_FILLER,
201
                     rolenames.ROLE_FILLER,
202
                     rolenames.ROLE_DIALOG,
203
                     rolenames.ROLE_APPLICATION]
204
205 776
        rolesList2 = [rolenames.ROLE_COMBO_BOX,
206
                     rolenames.ROLE_PANEL,
207
                     rolenames.ROLE_FILLER,
208
                     rolenames.ROLE_FILLER,
209
                     rolenames.ROLE_DIALOG,
210
                     rolenames.ROLE_APPLICATION]
211
212 776
        if (self.isDesiredFocusedItem(obj, rolesList1) \
213
            and obj.name == _("Find")) \
214
            or (self.isDesiredFocusedItem(obj, rolesList2) \
215
                and obj.parent.parent.parent.parent.name == _("Find")):
216 0
            return True
217
        else:
218 776
            return False
219
220
    # This method tries to detect and handle the following cases:
221
    # 1) Text area (for caching handle for spell checking purposes).
222
    # 2) Check Spelling Dialog.
223
224 1
    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
225
        """Called when the visual object with focus changes.
226
227
        Arguments:
228
        - event: if not None, the Event that caused the change
229
        - oldLocusOfFocus: Accessible that is the old locus of focus
230
        - newLocusOfFocus: Accessible that is the new locus of focus
231
        """
232
233 105
        brailleGen = self.brailleGenerator
234 105
        speechGen = self.speechGenerator
235
236 105
        debug.printObjectEvent(self.debugLevel,
237 105
                               event,
238 105
                               event.source.toString())
239
240
        # self.printAncestry(event.source)
241
242
        # 1) Text area (for caching handle for spell checking purposes).
243
        #
244
        # This works in conjunction with code in section 2). Check to see if
245
        # focus is currently in the gedit text area. If it is, then, if this
246
        # is the first time, save a pointer to the scroll pane which contains
247
        # the text being editted.
248
        #
249
        # Note that this drops through to then use the default event
250
        # processing in the parent class for this "focus:" event.
251
252 105
        rolesList = [rolenames.ROLE_TEXT,
253
                     rolenames.ROLE_SCROLL_PANE,
254
                     rolenames.ROLE_FILLER,
255
                     rolenames.ROLE_PAGE_TAB,
256
                     rolenames.ROLE_PAGE_TAB_LIST,
257
                     rolenames.ROLE_SPLIT_PANE]
258 105
        if self.isDesiredFocusedItem(event.source, rolesList):
259 7
            debug.println(self.debugLevel,
260 7
                          "gedit.locusOfFocusChanged - text area.")
261
262 7
            self.textArea = event.source.parent
263
            # Fall-thru to process the event with the default handler.
264
265
        # 2) check spelling dialog.
266
        #
267
        # Check to see if the Spell Check dialog has just appeared and got
268
        # focus. If it has, then speak/braille the current misspelt word
269
        # plus its context.
270
        #
271
        # Note that in order to make sure that this focus event is for the
272
        # check spelling dialog, a check is made of the localized name of the
273
        # option pane. Translators for other locales will need to ensure that
274
        # their translation of this string matches what gedit uses in
275
        # that locale.
276
277 105
        rolesList = [rolenames.ROLE_TEXT,
278
                     rolenames.ROLE_FILLER,
279
                     rolenames.ROLE_PANEL,
280
                     rolenames.ROLE_FILLER,
281
                     rolenames.ROLE_FRAME]
282 105
        if self.isDesiredFocusedItem(event.source, rolesList):
283 0
            frame = event.source.parent.parent.parent.parent
284 0
            if frame.name.startswith(_("Check Spelling")):
285 0
                debug.println(self.debugLevel,
286 0
                        "gedit.locusOfFocusChanged - check spelling dialog.")
287
288 0
                self.readMisspeltWord(event, event.source.parent.parent)
289
                # Fall-thru to process the event with the default handler.
290
291
        # For everything else, pass the focus event onto the parent class
292
        # to be handled in the default way.
293
294 105
        default.Script.locusOfFocusChanged(self, event,
295 105
                                           oldLocusOfFocus, newLocusOfFocus)
296
297
    # This method tries to detect and handle the following cases:
298
    # 1) check spelling dialog.
299
    # 2) find dialog - phrase not found.
300
301 1
    def onNameChanged(self, event):
302
        """Called whenever a property on an object changes.
303
304
        Arguments:
305
        - event: the Event
306
        """
307
308 310
        brailleGen = self.brailleGenerator
309 310
        speechGen = self.speechGenerator
310
311 310
        debug.printObjectEvent(self.debugLevel,
312 310
                               event,
313 310
                               event.source.toString())
314
315
        # self.printAncestry(event.source)
316
317
        # 1) check spelling dialog.
318
        #
319
        # Check to see if if we've had a property-change event for the
320
        # accessible name for the label containing the current misspelt
321
        # word in the check spelling dialog.
322
        # This (hopefully) means that the user has just corrected a
323
        # spelling mistake, in which case, speak/braille the current
324
        # misspelt word plus its context.
325
        #
326
        # Note that in order to make sure that this event is for the
327
        # check spelling dialog, a check is made of the localized name of the
328
        # frame. Translators for other locales will need to ensure that
329
        # their translation of this string matches what gedit uses in
330
        # that locale.
331
332 298
        rolesList = [rolenames.ROLE_LABEL,
333
                     rolenames.ROLE_PANEL,
334
                     rolenames.ROLE_FILLER,
335
                     rolenames.ROLE_FRAME]
336 298
        if self.isDesiredFocusedItem(event.source, rolesList):
337 0
            frame = event.source.parent.parent.parent
338 0
            if frame.name.startswith(_("Check Spelling")):
339 0
                debug.println(self.debugLevel,
340 0
                      "gedit.onNameChanged - check spelling dialog.")
341
342 0
                self.readMisspeltWord(event, event.source.parent)
343
                # Fall-thru to process the event with the default handler.
344
345
        # 2) find dialog - phrase not found.
346
        #
347
        # If we've received an "object:property-change:accessible-name" for
348
        # the status bar and the current locus of focus is on the Find
349
        # button on the Find dialog or the combo box in the Find dialog
350
        # and the last input event was a Return and the name for the current
351
        # event source is "Phrase not found", then speak it.
352
        #
353
        # [[[TODO: richb - "Phrase not found" is spoken twice because we
354
        # apparently get two identical "object:property-change:accessible-name"
355
        # events.]]]
356
357 298
        if event.source.role == rolenames.ROLE_STATUSBAR \
358
           and self.isFocusOnFindDialog() \
359
           and orca_state.lastInputEvent.event_string == "Return" \
360
           and event.source.name == _("Phrase not found"):
361 0
                debug.println(self.debugLevel,
362 0
                              "gedit.onNameChanged - phrase not found.")
363
364 0
                speech.speak(event.source.name)
365
366
        # Pass the event onto the parent class to be handled in the default way.
367 298
        default.Script.onNameChanged(self, event)
368
369 1
    def onStateChanged(self, event):
370
        """Called whenever an object's state changes.
371
372
        Arguments:
373
        - event: the Event
374
        """
375
376
        # Sometimes an object will tell us it is focused this
377
        # way versus issuing a focus event.  GEdit's edit area,
378
        # for example, will do this: when you use metacity's
379
        # window menu to do things like maximize or unmaximize
380
        # a window, you will only get a state-changed event
381
        # from the text area when it regains focus.
382
        # (See bug #350854 for more details).
383
        #
384 374
        if (event.type == "object:state-changed:focused") \
385
           and (event.detail1):
386 18
            orca.setLocusOfFocus(event, event.source)
387
388 374
        default.Script.onStateChanged(self, event)
389
390
    # This method tries to detect and handle the following cases:
391
    # 1) find dialog - phrase found.
392
393 1
    def onCaretMoved(self, event):
394
        """Called whenever the caret moves.
395
396
        Arguments:
397
        - event: the Event
398
        """
399
400 534
        debug.printObjectEvent(self.debugLevel,
401 534
                               event,
402 534
                               event.source.toString())
403
404
        # self.printAncestry(event.source)
405
406
        # If we've received a text caret moved event and the current locus
407
        # of focus is on the Find button on the Find dialog or the combo
408
        # box in the Find dialog and the last input event was a Return,
409
        # and if the current line contains the phrase we were looking for,
410
        # then speak the current text line, to give an indication of what
411
        # we've just found.
412
        #
413 534
        if self.isFocusOnFindDialog() \
414
           and orca_state.lastInputEvent.event_string == "Return":
415 0
            debug.println(self.debugLevel, "gedit.onCaretMoved - find dialog.")
416
417 0
            allComboBoxes = self.findByRole(orca_state.locusOfFocus.app,
418 0
                                            rolenames.ROLE_COMBO_BOX)
419 0
            phrase = self.getDisplayedText(allComboBoxes[0])
420 0
            [text, caretOffset, startOffset] = \
421
                self.getTextLineAtCaret(event.source)
422 0
            if text.lower().find(phrase) != -1:
423 0
                speech.speak(_("Phrase found."))
424 0
                utterances = self.speechGenerator.getSpeech(event.source, True)
425 0
                speech.speakUtterances(utterances)
426
427
        # For everything else, pass the caret moved event onto the parent
428
        # class to be handled in the default way.
429
430 534
        default.Script.onCaretMoved(self, event)