Coverage Report - orca.scripts.StarOffice

ModuleCoverage %
orca.scripts.StarOffice
35%
1
# Orca
2
#
3
# Copyright 2005-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 StarOffice and OpenOffice."""
21
22 1
__id__        = "$Id: StarOffice.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.chnames as chnames
31 1
import orca.default as default
32 1
import orca.input_event as input_event
33 1
import orca.rolenames as rolenames
34 1
import orca.braille as braille
35 1
import orca.braillegenerator as braillegenerator
36 1
import orca.orca as orca
37 1
import orca.orca_state as orca_state
38 1
import orca.speech as speech
39 1
import orca.speechgenerator as speechgenerator
40 1
import orca.settings as settings
41 1
import orca.keybindings as keybindings
42
43 1
from orca.orca_i18n import _ # for gettext support
44
45 1
inputLineForCell = None
46
47
# Dictionaries for the calc dynamic row and column headers.
48
#
49 1
dynamicColumnHeaders = {}
50 1
dynamicRowHeaders = {}
51
52 1
def adjustForWriterTable(obj):
53
    """Check to see if we are in Writer, where the object with focus is a
54
    paragraph, and the parent is the table cell. If it is, then, return the
55
    parent table cell otherwise return the current object.
56
57
    Arguments:
58
    - obj: the accessible object to check.
59
60
    Returns parent table cell (if in a Writer table ) or the current object.
61
    """
62
63 11
    if obj.role == rolenames.ROLE_PARAGRAPH and \
64
       obj.parent.role == rolenames.ROLE_TABLE_CELL:
65 0
        return obj.parent
66
    else:
67 11
        return obj
68
69 1
def getTable(obj):
70
    """Get the table that this table cell is in.
71
72
    Arguments:
73
    - obj: the table cell.
74
75
    Return the table that this table cell is in, or None if this object
76
    isn't in a table.
77
    """
78
79 11
    table = None
80 11
    obj = adjustForWriterTable(obj)
81 11
    if obj.role == rolenames.ROLE_TABLE_CELL and obj.parent:
82 11
        table = obj.parent.table
83
84 11
    return table
85
86 1
def getDynamicColumnHeaderCell(obj, column):
87
    """Given a table cell, return the dynamic column header cell associated
88
    with it.
89
90
    Arguments:
91
    - obj: the table cell.
92
    - column: the column that this dynamic header is on.
93
94
    Return the dynamic column header cell associated with the given table cell.
95
    """
96
97 0
    obj = adjustForWriterTable(obj)
98 0
    accCell = None
99 0
    parent = obj.parent
100 0
    if parent and parent.table:
101 0
        row = parent.table.getRowAtIndex(obj.index)
102 0
        cell = parent.table.getAccessibleAt(row, column)
103 0
        accCell = atspi.Accessible.makeAccessible(cell)
104
105 0
    return accCell
106
107 1
def getDynamicRowHeaderCell(obj, row):
108
    """Given a table cell, return the dynamic row header cell associated
109
    with it.
110
111
    Arguments:
112
    - obj: the table cell.
113
    - row: the row that this dynamic header is on.
114
115
    Return the dynamic row header cell associated with the given table cell.
116
    """
117
118 0
    obj = adjustForWriterTable(obj)
119 0
    accCell = None
120 0
    parent = obj.parent
121 0
    if parent and parent.table:
122 0
        column = parent.table.getColumnAtIndex(obj.index)
123 0
        cell = parent.table.getAccessibleAt(row, column)
124 0
        accCell = atspi.Accessible.makeAccessible(cell)
125
126 0
    return accCell
127
128 1
def locateInputLine(obj):
129
    """Return the spread sheet input line. This only needs to be found
130
    the very first time a spread sheet table cell gets focus. We use the
131
    table cell to work back up the component hierarchy until we have found
132
    the common panel that both it and the input line reside in. We then
133
    use that as the base component to search for a component which has a
134
    paragraph role. This will be the input line.
135
136
    Arguments:
137
    - obj: the spread sheet table cell that has just got focus.
138
139
    Returns the spread sheet input line component.
140
    """
141
142 0
    inputLine = None
143 0
    panel = obj.parent.parent.parent.parent
144 0
    if panel and panel.role == rolenames.ROLE_PANEL:
145 0
        allParagraphs = self.findByRole(panel, rolenames.ROLE_PARAGRAPH)
146 0
        if len(allParagraphs) == 1:
147 0
            inputLine = allParagraphs[0]
148
        else:
149 0
            debug.println(debug.LEVEL_SEVERE,
150 0
                  "StarOffice: locateInputLine: incorrect paragraph count.")
151
    else:
152 0
        debug.println(debug.LEVEL_SEVERE,
153 0
                  "StarOffice: locateInputLine: couldn't find common panel.")
154
155 0
    return inputLine
156
157 1
def isSpreadSheetCell(obj):
158
    """Return an indication of whether the given obj is a spread sheet
159
    table cell.
160
161
    Arguments:
162
    - obj: the object to check.
163
164
    Returns True if this is a table cell, False otherwise.
165
    """
166
167 11
    found = False
168 11
    rolesList = [rolenames.ROLE_TABLE_CELL, \
169
                 rolenames.ROLE_TABLE, \
170
                 rolenames.ROLE_UNKNOWN, \
171
                 rolenames.ROLE_SCROLL_PANE, \
172
                 rolenames.ROLE_PANEL, \
173
                 rolenames.ROLE_ROOT_PANE, \
174
                 rolenames.ROLE_FRAME, \
175
                 rolenames.ROLE_APPLICATION]
176 11
    if self.isDesiredFocusedItem(obj, rolesList):
177
        # We've found a table cell with the correct hierarchy. Now check
178
        # that we are in a spreadsheet as opposed to the writer application.
179
        # See bug #382408.
180
        #
181 0
        current = obj.parent
182 0
        while current.role != rolenames.ROLE_APPLICATION:
183 0
            if current.role == rolenames.ROLE_FRAME and \
184
               (current.name and current.name.endswith(_("Calc"))):
185 0
                found = True
186 0
            current = current.parent
187
188 0
    return found
189
190 2
class BrailleGenerator(braillegenerator.BrailleGenerator):
191
    """Overrides _getBrailleRegionsForTableCellRow so that , when we are
192
    in a spread sheet, we can braille the dynamic row and column headers
193
    (assuming they are set).
194
    Overrides _getBrailleRegionsForTableCell so that, when we are in
195
    a spread sheet, we can braille the location of the table cell as well
196
    as the contents.
197
    """
198
199 1
    def __init__(self, script):
200 4
        braillegenerator.BrailleGenerator.__init__(self, script)
201
202 1
    def _getBrailleRegionsForTableCellRow(self, obj):
203
        """Get the braille for a table cell row or a single table cell
204
        if settings.readTableCellRow is False.
205
206
        Arguments:
207
        - obj: the table cell
208
209
        Returns a list where the first element is a list of Regions to display
210
        and the second element is the Region which should get focus.
211
        """
212
213 0
        global dynamicColumnHeaders, dynamicRowHeaders
214 0
        global getColumnHeaderCell, getRowHeaderCell, isSpreadSheetCell
215
216 11
        regions = []
217
218
        # Check to see if this spread sheet cell has either a dynamic
219
        # column heading or row heading (or both) associated with it.
220
        # If it does, then braille those first before brailling the
221
        # cell contents.
222
        #
223 11
        table = getTable(obj)
224 11
        parent = obj.parent
225
226 11
        if parent.__dict__.has_key("lastColumn") and \
227
           parent.lastColumn != parent.table.getColumnAtIndex(obj.index):
228 0
            if dynamicColumnHeaders.has_key(table):
229 0
                row = dynamicColumnHeaders[table]
230 0
                header = getDynamicRowHeaderCell(obj, row)
231 0
                if header.childCount > 0:
232 0
                    for i in range(0, header.childCount):
233 0
                        child = header.child(i)
234 0
                        text = self._script.getText(child, 0, -1)
235 0
                        if text:
236 0
                            regions.append(braille.Region(" " + text + " "))
237 0
                elif header.text:
238 0
                    text = self._script.getText(header, 0, -1)
239 0
                    if text:
240 0
                        regions.append(braille.Region(" " + text + " "))
241
242 11
        if parent.__dict__.has_key("lastRow") and \
243
           parent.lastRow != parent.table.getRowAtIndex(obj.index):
244 0
            if dynamicRowHeaders.has_key(table):
245 0
                column = dynamicRowHeaders[table]
246 0
                header = getDynamicColumnHeaderCell(obj, column)
247 0
                if header.childCount > 0:
248 0
                    for i in range(0, header.childCount):
249 0
                        child = header.child(i)
250 0
                        text = self._script.getText(child, 0, -1)
251 0
                        if text:
252 0
                            regions.append(braille.Region(" " + text + " "))
253 0
                elif header.text:
254 0
                    text = self._script.getText(header, 0, -1)
255 0
                    if text:
256 0
                        regions.append(braille.Region(" " + text + " "))
257
258 11
        if isSpreadSheetCell(obj):
259
260
            # Adding in a check here to make sure that the parent is a
261
            # valid table. It's possible that the parent could be a
262
            # table cell too (see bug #351501).
263
            #
264 0
            if settings.readTableCellRow and obj.parent.table:
265 0
                rowRegions = []
266 0
                savedBrailleVerbosityLevel = settings.brailleVerbosityLevel
267 0
                settings.brailleVerbosityLevel = \
268
                                             settings.VERBOSITY_LEVEL_BRIEF
269
270 0
                parent = obj.parent
271 0
                row = parent.table.getRowAtIndex(obj.index)
272 0
                column = parent.table.getColumnAtIndex(obj.index)
273
274
                # This is an indication of whether we should speak all the
275
                # table cells (the user has moved focus up or down a row),
276
                # or just the current one (focus has moved left or right in
277
                # the same row).
278
                #
279 0
                speakAll = True
280 0
                if parent.__dict__.has_key("lastRow") and \
281
                    parent.__dict__.has_key("lastColumn"):
282 0
                    speakAll = (parent.lastRow != row) or \
283
                           ((row == 0 or row == parent.table.nRows-1) and \
284
                            parent.lastColumn == column)
285
286 0
                if speakAll:
287 0
                    focusRowRegion = None
288 0
                    for i in range(0, parent.table.nColumns):
289 0
                        accRow = parent.table.getAccessibleAt(row, i)
290 0
                        cell = atspi.Accessible.makeAccessible(accRow)
291 0
                        showing = cell.state.count( \
292
                                        atspi.Accessibility.STATE_SHOWING)
293 0
                        if showing:
294 0
                            [cellRegions, focusRegion] = \
295
                                self._getBrailleRegionsForTableCell(cell)
296 0
                            if len(rowRegions):
297 0
                                rowRegions.append(braille.Region(" "))
298 0
                            rowRegions.append(cellRegions[0])
299 0
                            if i == column:
300 0
                                focusRowRegion = cellRegions[0]
301 0
                    regions.extend(rowRegions)
302 0
                    settings.brailleVerbosityLevel = savedBrailleVerbosityLevel
303
                else:
304 0
                    [cellRegions, focusRegion] = \
305
                                self._getBrailleRegionsForTableCell(obj)
306 0
                    regions.extend(cellRegions)
307
            else:
308 0
                [cellRegions, focusRegion] = \
309
                                self._getBrailleRegionsForTableCell(obj)
310 0
                regions.extend(cellRegions)
311 0
            regions = [regions, focusRegion]
312
        else:
313 0
            brailleGen = braillegenerator.BrailleGenerator
314 0
            [cellRegions, focusRegion] = \
315
                brailleGen._getBrailleRegionsForTableCellRow(self, obj)
316 0
            regions.extend(cellRegions)
317 0
            regions = [regions, focusRegion]
318
319 0
        return regions
320
321 1
    def _getBrailleRegionsForTableCell(self, obj):
322
        """Get the braille for a table cell. If this isn't inside a
323
        spread sheet, just return the regions returned by the default
324
        table cell braille handler.
325
326
        Arguments:
327
        - obj: the table cell
328
329
        Returns a list where the first element is a list of Regions to display
330
        and the second element is the Region which should get focus.
331
        """
332
333 0
        global inputLineForCell, isSpreadSheetCell, locateInputLine
334
335 0
        if isSpreadSheetCell(obj):
336 0
            if inputLineForCell == None:
337 0
                inputLineForCell = locateInputLine(obj)
338
339 0
            regions = []
340 0
            text = self.getDisplayedText(obj)
341 0
            componentRegion = braille.Component(obj, text)
342 0
            regions.append(componentRegion)
343
344
            # If the spread sheet table cell has something in it, then we
345
            # want to append the name of the cell (which will be its location).
346
            # Note that if the cell was empty, then self.getDisplayedText will
347
            # have already done this for us.
348
            #
349 0
            if obj.text:
350 0
                objectText = self._script.getText(obj, 0, -1)
351 0
                if objectText and len(objectText) != 0:
352 0
                    regions.append(braille.Region(" " + obj.name))
353
354 0
            return [regions, componentRegion]
355
356
        else:
357
            # Check to see how many children this table cell has. If it's
358
            # just one (or none), then pass it on to the superclass to be
359
            # processed.
360
            #
361
            # If it's more than one, then get the braille regions for each 
362
            # child, and call this method again.
363
            #
364 0
            if obj.childCount <= 1:
365 0
                brailleGen = braillegenerator.BrailleGenerator
366 0
                regions = brailleGen._getBrailleRegionsForTableCell(self, obj)
367
            else:
368 0
                regions = []
369 0
                for i in range(0, obj.childCount):
370 0
                    child = obj.child(i)
371 0
                    [cellRegions, focusRegion] = \
372
                                self._getBrailleRegionsForTableCell(child)
373 0
                    regions.extend(cellRegions)
374 0
                return [regions, focusRegion]
375
376 0
        return regions
377
378 2
class SpeechGenerator(speechgenerator.SpeechGenerator):
379
    """Overrides _getSpeechForComboBox so that we can provide a name for
380
    the Calc Name combo box.
381
    Overrides _getSpeechForTableCellRow so that , when we are in a
382
    spread sheet, we can speak the dynamic row and column headers
383
    (assuming they are set).
384
    Overrides _getSpeechForTableCell so that, when we are in a spread
385
    sheet, we can speak the location of the table cell as well as the
386
    contents.
387
    Overrides _getSpeechForToggleButton so that, when the toggle buttons
388
    on the Formatting toolbar change state, we provide both the name and
389
    the state (as "on" or "off")
390
    Overrides _getSpeechForPushButton because sometimes the toggle buttons
391
    on the Formatting toolbar claim to be push buttons.
392
    """
393 1
    def __init__(self, script):
394 4
        speechgenerator.SpeechGenerator.__init__(self, script)
395
396 1
    def _getSpeechForComboBox(self, obj, already_focused):
397
        """Get the speech for a combo box. If the combo box already has focus,
398
        then only the selection is spoken.
399
        Also provides a name for the OOo Calc Name combo box. This name is
400
        provided in clause 5) of locusOfFocusChanged() below.
401
402
        Arguments:
403
        - obj: the combo box
404
        - already_focused: False if object just received focus
405
406
        Returns a list of utterances to be spoken for the object.
407
        """
408
409 2
        utterances = []
410
411 2
        if not already_focused:
412 2
            label = self._getSpeechForObjectLabel(obj)
413 2
            if not label:
414 2
                label = [ obj.name ]
415 2
            utterances.extend(label)
416
        else:
417 0
            label = None
418
419 2
        name = self._getSpeechForObjectName(obj)
420 2
        if name != label:
421 0
            utterances.extend(name)
422
423 2
        if not already_focused:
424 2
            utterances.extend(self._getSpeechForObjectRole(obj))
425
426 2
        utterances.extend(self._getSpeechForObjectAvailability(obj))
427
428 2
        self._debugGenerator("_getSpeechForComboBox",
429 2
                             obj,
430 2
                             already_focused,
431 2
                             utterances)
432
433 2
        return utterances
434
435 1
    def _getSpeechForTableCellRow(self, obj, already_focused):
436
        """Get the speech for a table cell row or a single table cell
437
        if settings.readTableCellRow is False. If this isn't inside a
438
        spread sheet, just return the utterances returned by the default
439
        table cell speech handler.
440
441
        Arguments:
442
        - obj: the table cell
443
        - already_focused: False if object just received focus
444
445
        Returns a list of utterances to be spoken for the object.
446
        """
447
448 0
        global dynamicColumnHeaders, dynamicRowHeaders
449 0
        global getColumnHeaderCell, getRowHeaderCell, isSpreadSheetCell
450
451 0
        utterances = []
452
453 0
        if not already_focused:
454
455
            # Check to see if this spread sheet cell has either a dynamic
456
            # column heading or row heading (or both) associated with it.
457
            # If it does, then speak those first before speaking the cell
458
            # contents.
459
            #
460 0
            table = getTable(obj)
461 0
            parent = obj.parent
462
463 0
            if parent.__dict__.has_key("lastColumn") and \
464
               parent.lastColumn != \
465
               parent.table.getColumnAtIndex(obj.index):
466 0
                if dynamicColumnHeaders.has_key(table):
467 0
                    row = dynamicColumnHeaders[table]
468 0
                    header = getDynamicRowHeaderCell(obj, row)
469 0
                    if header.childCount > 0:
470 0
                        for i in range(0, header.childCount):
471 0
                            child = header.child(i)
472 0
                            text = self._script.getText(child, 0, -1)
473 0
                            if text:
474 0
                                utterances.append(text)
475 0
                    elif header.text:
476 0
                        text = self._script.getText(header, 0, -1)
477 0
                        if text:
478 0
                            utterances.append(text)
479
480 0
            if parent.__dict__.has_key("lastRow") and \
481
               parent.lastRow != parent.table.getRowAtIndex(obj.index):
482 0
                if dynamicRowHeaders.has_key(table):
483 0
                    column = dynamicRowHeaders[table]
484 0
                    header = getDynamicColumnHeaderCell(obj, column)
485 0
                    if header.childCount > 0:
486 0
                        for i in range(0, header.childCount):
487 0
                            child = header.child(i)
488 0
                            text = self._script.getText(child, 0, -1)
489 0
                            if text:
490 0
                                utterances.append(text)
491 0
                    elif header.text:
492 0
                        text = self._script.getText(header, 0, -1)
493 0
                        if text:
494 0
                            utterances.append(text)
495
496 0
        if isSpreadSheetCell(obj):
497 0
            if not already_focused:
498 0
                if settings.readTableCellRow:
499 0
                    parent = obj.parent
500 0
                    row = parent.table.getRowAtIndex(obj.index)
501 0
                    column = parent.table.getColumnAtIndex(obj.index)
502
503
                    # This is an indication of whether we should speak all the
504
                    # table cells (the user has moved focus up or down a row),
505
                    # or just the current one (focus has moved left or right in
506
                    # the same row).
507
                    #
508 0
                    speakAll = True
509 0
                    if parent.__dict__.has_key("lastRow") and \
510
                        parent.__dict__.has_key("lastColumn"):
511 0
                        speakAll = (parent.lastRow != row) or \
512
                               ((row == 0 or row == parent.table.nRows-1) and \
513
                                parent.lastColumn == column)
514
515 0
                    if speakAll:
516 0
                        for i in range(0, parent.table.nColumns):
517 0
                            accRow = parent.table.getAccessibleAt(row, i)
518 0
                            cell = atspi.Accessible.makeAccessible(accRow)
519 0
                            showing = cell.state.count( \
520
                                          atspi.Accessibility.STATE_SHOWING)
521 0
                            if showing:
522 0
                                utterances.extend(self._getSpeechForTableCell(\
523
                                                  cell, already_focused))
524
                    else:
525 0
                        utterances.extend(self._getSpeechForTableCell(obj,
526 0
                                                             already_focused))
527
                else:
528 0
                    utterances.extend(self._getSpeechForTableCell(obj,
529 0
                                                             already_focused))
530
        else:
531 0
            speechGen = speechgenerator.SpeechGenerator
532 0
            utterances.extend(speechGen._getSpeechForTableCellRow(self, obj,
533 0
                                                             already_focused))
534
535 0
        return utterances
536
537 1
    def _getSpeechForTableCell(self, obj, already_focused):
538
        """Get the speech for a table cell. If this isn't inside a
539
        spread sheet, just return the utterances returned by the default
540
        table cell speech handler.
541
542
        Arguments:
543
        - obj: the table cell
544
        - already_focused: False if object just received focus
545
546
        Returns a list of utterances to be spoken for the object.
547
        """
548
549 0
        global inputLineForCell, isSpreadSheetCell, locateInputLine
550
551 0
        if isSpreadSheetCell(obj):
552 0
            utterances = []
553
554 0
            if inputLineForCell == None:
555 0
                inputLineForCell = locateInputLine(obj)
556
557 0
            if obj.text:
558 0
                objectText = self._script.getText(obj, 0, -1)
559 0
                utterances.append(objectText)
560
561 0
            nameList = obj.name.split()
562 0
            utterances.append(nameList[1])
563
        else:
564
            # Check to see how many children this table cell has. If it's
565
            # just one (or none), then pass it on to the superclass to be 
566
            # processed.
567
            #
568
            # If it's more than one, then get the speech for each child,
569
            # and call this method again.
570
            #
571 0
            if obj.childCount <= 1:
572 0
                speechGen = speechgenerator.SpeechGenerator
573 0
                utterances = speechGen._getSpeechForTableCell(self, obj,
574 0
                                                        already_focused)
575
            else:
576 0
                utterances = []
577 0
                for i in range(0, obj.childCount):
578 0
                    child = obj.child(i)
579 0
                    utterances.extend(self._getSpeechForTableCell(child,
580 0
                                                        already_focused))
581
582 0
        return utterances
583
584 1
    def _getSpeechForToggleButton(self, obj, already_focused):
585
        """Get the speech for a toggle button.  We always want to speak the
586
        state if it's on a toolbar.
587
588
        Arguments:
589
        - obj: the toggle button
590
        - already_focused: False if object just received focus
591
592
        Returns a list of utterances to be spoken for the object.
593
        """
594
595 4
        utterances = []
596 4
        if obj.parent.role == rolenames.ROLE_TOOL_BAR:
597 0
            if obj.state.count(atspi.Accessibility.STATE_CHECKED):
598 0
                checkedState = _("on")
599
            else:
600 0
                checkedState = _("off")
601
602 0
            utterances.append(obj.name)
603 0
            utterances.append(checkedState)
604
        else:
605 4
            speechGen = speechgenerator.SpeechGenerator
606 4
            utterances.extend(speechGen._getSpeechForToggleButton(self, obj,
607 4
                                                             already_focused))
608
609 4
        return utterances
610
611 1
    def _getSpeechForPushButton(self, obj, already_focused):
612
        """Get the speech for a push button.  We always want to speak the
613
        state if it's on a toolbar.
614
615
        Arguments:
616
        - obj: the push button
617
        - already_focused: False if object just received focus
618
619
        Returns a list of utterances to be spoken for the object.
620
        """
621
622 8
        utterances = []
623 8
        if obj.parent.role == rolenames.ROLE_TOOL_BAR:
624 0
            if obj.state.count(atspi.Accessibility.STATE_CHECKED):
625 0
                checkedState = _("on")
626
            else:
627 0
                checkedState = _("off")
628
629 0
            utterances.append(obj.name)
630 0
            utterances.append(checkedState)
631
        else:
632 8
            speechGen = speechgenerator.SpeechGenerator
633 8
            utterances.extend(speechGen._getSpeechForPushButton(self, obj,
634 8
                                                             already_focused))
635
636 8
        return utterances
637
638
########################################################################
639
#                                                                      #
640
# The StarOffice script class.                                         #
641
#                                                                      #
642
########################################################################
643
644 2
class Script(default.Script):
645
646 1
    def __init__(self, app):
647
        """Creates a new script for the given application.
648
649
        Arguments:
650
        - app: the application to create a script for.
651
        """
652
653 4
        default.Script.__init__(self, app)
654
655
        # Set the debug level for all the methods in this script.
656
        #
657 4
        self.debugLevel = debug.LEVEL_FINEST
658
659
        # A handle to the last spread sheet cell encountered.
660
        #
661 4
        self.lastCell = None
662
663
        # The following variables will be used to try to determine if we've
664
        # already handled this misspelt word (see readMisspeltWord() for
665
        # more details.
666
667 4
        self.lastTextLength = -1
668 4
        self.lastBadWord = ''
669 4
        self.lastStartOff = -1
670 4
        self.lastEndOff = -1
671
672
        # Used to determine if the user has double-clicked the dynamic
673
        # row/column hotkeys.
674
675 4
        self.lastDynamicEvent = None
676
677
        # Used to determine whether the caret has moved to a new paragraph.
678
        #
679 4
        self.currentParagraph = None
680
681
        # Set the number of retries after a COMM_FAILURE to 1. The default
682
        # of 5 was not allowing Orca to be responsive in the event of OOo
683
        # going into crash recovery mode (see bug #397787).
684
        #
685 4
        self.commFailureAttemptLimit = 1
686
687
        # The default set of text attributes to speak to the user. The
688
        # only difference over the default set in settings.py is to add
689
        # in "left-margin:" and "right-margin:".
690
691 4
        settings.enabledTextAttributes = "size:; family-name:; weight:400; indent:0; left-margin:0; right-margin:0; underline:none; strikethrough:false; justification:left; style:normal;"
692
693
        # [[[TODO: JD - HACK because we won't get events from toggle
694
        # buttons on the Formatting toolbar until we "tickle/poke"
695
        # the hierarchy. But we only want to do it once.  
696
        # See bug #363830 and OOo issue #70872.]]]
697
        #
698 4
        self.tickled = None
699
700 1
    def getBrailleGenerator(self):
701
        """Returns the braille generator for this script.
702
        """
703
704 4
        return BrailleGenerator(self)
705
706 1
    def getSpeechGenerator(self):
707
        """Returns the speech generator for this script.
708
        """
709
710 4
        return SpeechGenerator(self)
711
712 1
    def setupInputEventHandlers(self):
713
        """Defines InputEventHandler fields for this script that can be
714
        called by the key and braille bindings. In this particular case,
715
        we just want to be able to add a handler to return the contents of
716
        the input line.
717
        """
718
719 4
        default.Script.setupInputEventHandlers(self)
720
721 4
        self.inputEventHandlers["speakInputLineHandler"] = \
722
            input_event.InputEventHandler(
723
                Script.speakInputLine,
724 4
                _("Speaks the contents of the input line."))
725
726 4
        self.inputEventHandlers["setDynamicColumnHeadersHandler"] = \
727
            input_event.InputEventHandler(
728
                Script.setDynamicColumnHeaders,
729 4
                _("Set the row to use as dynamic column headers when speaking calc cells."))
730
731 4
        self.inputEventHandlers["setDynamicRowHeadersHandler"] = \
732
            input_event.InputEventHandler(
733
                Script.setDynamicRowHeaders,
734 4
                _("Set the column to use as dynamic row headers to use when speaking calc cells."))
735
736 1
    def getKeyBindings(self):
737
        """Defines the key bindings for this script. Setup the default
738
        key bindings, then add one in for reading the input line.
739
740
        Returns an instance of keybindings.KeyBindings.
741
        """
742
743 4
        keyBindings = default.Script.getKeyBindings(self)
744
745 4
        keyBindings.add(
746
            keybindings.KeyBinding(
747
                "a",
748 4
                1 << settings.MODIFIER_ORCA,
749 4
                1 << settings.MODIFIER_ORCA,
750 4
                self.inputEventHandlers["speakInputLineHandler"]))
751
752 4
        keyBindings.add(
753
            keybindings.KeyBinding(
754
                "r",
755 4
                1 << settings.MODIFIER_ORCA,
756 4
                1 << settings.MODIFIER_ORCA,
757 4
                self.inputEventHandlers["setDynamicColumnHeadersHandler"]))
758
759 4
        keyBindings.add(
760
            keybindings.KeyBinding(
761
                "c",
762 4
                1 << settings.MODIFIER_ORCA,
763 4
                1 << settings.MODIFIER_ORCA,
764 4
                self.inputEventHandlers["setDynamicRowHeadersHandler"]))
765
766 4
        return keyBindings
767
768 1
    def checkForTableBoundry(self, oldFocus, newFocus):
769
        """Check to see if we've entered or left a table.
770
        When entering a table, announce the table dimensions.
771
        When leaving a table, announce that the table has been exited.
772
773
        Arguments:
774
        - oldFocus: Accessible that is the old locus of focus
775
        - newFocus: Accessible that is the new locus of focus
776
        """
777
778 47
        if oldFocus == None or newFocus == None:
779 0
            return
780
781 47
        oldFocusIsTable = None 
782 285
        while oldFocus.role != rolenames.ROLE_APPLICATION:
783 238
            if oldFocus.role == rolenames.ROLE_TABLE:
784 0
                oldFocusIsTable = oldFocus
785 0
                break
786 238
            oldFocus = oldFocus.parent 
787
788 47
        newFocusIsTable = None
789 306
        while newFocus.role != rolenames.ROLE_APPLICATION:
790 259
            if newFocus.role == rolenames.ROLE_TABLE:
791 0
                newFocusIsTable = newFocus
792 0
                break
793 259
            newFocus = newFocus.parent
794
795 47
        if oldFocusIsTable == None and newFocusIsTable != None:
796 0
            rows = newFocusIsTable.table.nRows
797 0
            columns = newFocusIsTable.table.nColumns
798 0
            line = _("table with %d rows and %d columns.") % (rows, columns)
799 0
            speech.speak(line)
800 47
        elif oldFocusIsTable != None and newFocusIsTable == None:
801 0
            speech.speak(_("leaving table."))
802
803 1
    def speakInputLine(self, inputEvent):
804
        """Speak the contents of the spread sheet input line (assuming we
805
        have a handle to it - generated when we first focus on a spread
806
        sheet table cell.
807
808
        This will be either the contents of the table cell that has focus
809
        or the formula associated with it.
810
811
        Arguments:
812
        - inputEvent: if not None, the input event that caused this action.
813
        """
814
815 0
        debug.println(self.debugLevel, "StarOffice.speakInputLine.")
816
817
        # Check to see if the current focus is a table cell.
818
        #
819 0
        if isSpreadSheetCell(orca_state.locusOfFocus):
820 0
            if inputLineForCell and inputLineForCell.text:
821 0
                inputLine = self.getText(inputLineForCell, 0, -1)
822 0
                if not inputLine:
823 0
                    inputLine = _("empty")
824 0
                debug.println(self.debugLevel,
825 0
                        "StarOffice.speakInputLine: contents: %s" % inputLine)
826 0
                speech.speak(inputLine)
827
828 1
    def getTableRow(self, cell):
829
        """Get the row number in the table that this table cell is on.
830
831
        Arguments:
832
        - cell: the table cell to get the row number for.
833
834
        Return the row number that this table cell is on, or None if
835
        this isn't a table cell.
836
        """
837
838 0
        row = None
839 0
        cell = adjustForWriterTable(cell)
840 0
        if cell.role == rolenames.ROLE_TABLE_CELL:
841 0
            parent = cell.parent
842 0
            if parent and parent.table:
843 0
                row = parent.table.getRowAtIndex(cell.index)
844
845 0
        return row
846
847 1
    def getTableColumn(self, cell):
848
        """Get the column number in the table that this table cell is on.
849
850
        Arguments:
851
        - cell: the table cell to get the column number for.
852
853
        Return the column number that this table cell is on, or None if
854
        this isn't a table cell.
855
        """
856
857 0
        column = None
858 0
        cell = adjustForWriterTable(cell)
859 0
        if cell.role == rolenames.ROLE_TABLE_CELL:
860 0
            parent = cell.parent
861 0
            if parent and parent.table:
862 0
                column = parent.table.getColumnAtIndex(cell.index)
863
864 0
        return column
865
866 1
    def setDynamicColumnHeaders(self, inputEvent):
867
        """Set the row for the dynamic header columns to use when speaking
868
        calc cell entries. In order to set the row, the user should first set
869
        focus to the row that they wish to define and then press Insert-r.
870
871
        Once the user has defined the row, it will be used to first speak
872
        this header when moving between columns.
873
874
        A "double-click" of the Insert-c hotkey, will clear the dynamic
875
        header column.
876
877
        Arguments:
878
        - inputEvent: if not None, the input event that caused this action.
879
        """
880
881 0
        global dynamicColumnHeaders, getTable
882
883 0
        debug.println(self.debugLevel, "StarOffice.setDynamicColumnHeaders.")
884
885 0
        clickCount = self.getClickCount(self.lastDynamicEvent, inputEvent)
886 0
        table = getTable(orca_state.locusOfFocus)
887 0
        if table:
888 0
            row = self.getTableRow(orca_state.locusOfFocus)
889 0
            if clickCount == 2:
890 0
                try:
891 0
                    del dynamicColumnHeaders[table]
892 0
                    line = _("Dynamic column header cleared.")
893 0
                    speech.speak(line)
894 0
                    braille.displayMessage(line)
895 0
                except:
896 0
                    pass
897
            else:
898 0
                dynamicColumnHeaders[table] = row
899 0
                line = _("Dynamic column header set for row ") + str(row+1)
900 0
                speech.speak(line)
901 0
                braille.displayMessage(line)
902 0
        self.lastDynamicEvent = inputEvent
903
904 0
        return True
905
906 1
    def columnConvert(self, column):
907
        """ Convert a spreadsheet column into it's column label
908
909
        Arguments:
910
        - column: the column number to convert.
911
912
        Returns a string representing the spread sheet column.
913
        """
914
915 0
        BASE26="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
916
917 0
        if column <= len(BASE26):
918 0
            return BASE26[column-1]
919
920 0
        res = ""
921 0
        while column > 0:
922 0
            digit = column % len(BASE26)
923 0
            res = " " + BASE26[digit-1] + res
924 0
            column /= len(BASE26)
925
926 0
        return res
927
928 1
    def setDynamicRowHeaders(self, inputEvent):
929
        """Set the column for the dynamic header rows to use when speaking
930
        calc cell entries. In order to set the column, the user should first
931
        set focus to the column that they wish to define and then press
932
        Insert-c.
933
934
        Once the user has defined the column, it will be used to first speak
935
        this header when moving between rows.
936
937
        A "double-click" of the Insert-r hotkey, will clear the dynamic
938
        header row.
939
940
        Arguments:
941
        - inputEvent: if not None, the input event that caused this action.
942
        """
943
944 0
        global dynamicRowHeaders, getTable
945
946 0
        debug.println(self.debugLevel, "StarOffice.setDynamicRowHeaders.")
947
948 0
        clickCount = self.getClickCount(self.lastDynamicEvent, inputEvent)
949 0
        table = getTable(orca_state.locusOfFocus)
950 0
        if table:
951 0
            column = self.getTableColumn(orca_state.locusOfFocus)
952 0
            if clickCount == 2:
953 0
                try:
954 0
                    del dynamicRowHeaders[table]
955 0
                    line = _("Dynamic row header cleared.")
956 0
                    speech.speak(line)
957 0
                    braille.displayMessage(line)
958 0
                except:
959 0
                    pass
960
            else:
961 0
                dynamicRowHeaders[table] = column
962 0
                line = _("Dynamic row header set for column %s") % \
963
                       self.columnConvert(column+1)
964 0
                speech.speak(line)
965 0
                braille.displayMessage(line)
966
967 0
        self.lastDynamicEvent = inputEvent
968
969 0
        return True
970
971 1
    def readMisspeltWord(self, event, pane):
972
        """Speak/braille the current misspelt word plus its context.
973
           The spell check dialog contains a "paragraph" which shows the
974
           context for the current spelling mistake. After speaking/brailling
975
           the default action for this component, that a selection of the
976
           surronding text from that paragraph with the misspelt word is also
977
           spoken.
978
979
        Arguments:
980
        - event: the event.
981
        - pane: the option pane in the spell check dialog.
982
        """
983
984 0
        paragraph = self.findByRole(pane, rolenames.ROLE_PARAGRAPH)
985
986
        # Determine which word is the misspelt word. This word will have
987
        # non-default text attributes associated with it.
988
989 0
        textLength = paragraph[0].text.characterCount
990 0
        startFound = False
991 0
        startOff = 0
992 0
        endOff = textLength
993 0
        for i in range(0, textLength):
994 0
            attributes = paragraph[0].text.getAttributes(i)
995 0
            if len(attributes[0]) != 0:
996 0
                if not startFound:
997 0
                    startOff = i
998 0
                    startFound = True
999
            else:
1000 0
                if startFound:
1001 0
                    endOff = i
1002 0
                    break
1003
1004 0
        badWord = self.getText(paragraph[0], startOff, endOff - 1)
1005
1006
        # Note that we often get two or more of these focus or property-change
1007
        # events each time there is a new misspelt word. We extract the
1008
        # length of the line of text, the misspelt word, the start and end
1009
        # offsets for that word and compare them against the values saved
1010
        # from the last time this routine was called. If they are the same
1011
        # then we ignore it.
1012
1013 0
        debug.println(self.debugLevel, \
1014 0
            "StarOffice.readMisspeltWord: type=%s  word=%s(%d,%d)  len=%d" % \
1015
            (event.type, badWord, startOff, endOff, textLength))
1016
1017 0
        if (textLength == self.lastTextLength) and \
1018
           (badWord == self.lastBadWord) and \
1019
           (startOff == self.lastStartOff) and \
1020
           (endOff == self.lastEndOff):
1021 0
            return
1022
1023
        # Create a list of all the words found in the misspelt paragraph.
1024
        #
1025 0
        text = self.getText(paragraph[0], 0, -1)
1026 0
        allTokens = text.split()
1027
1028 0
        self.speakMisspeltWord(allTokens, badWord)
1029
1030
        # Save misspelt word information for comparison purposes next
1031
        # time around.
1032
        #
1033 0
        self.lastTextLength = textLength
1034 0
        self.lastBadWord = badWord
1035 0
        self.lastStartOff = startOff
1036 0
        self.lastEndOff = endOff
1037
1038 1
    def endOfLink(self, obj, word, startOffset, endOffset):
1039
        """Return an indication of whether the given word contains the
1040
           end of a hypertext link.
1041
1042
        Arguments:
1043
        - obj: an Accessible object that implements the AccessibleText
1044
               interface
1045
        - word: the word to check
1046
        - startOffset: the start offset for this word
1047
        - endOffset: the end offset for this word
1048
1049
        Returns True if this word contains the end of a hypertext link.
1050
        """
1051
1052 0
        nLinks = obj.hypertext.getNLinks()
1053 0
        links = []
1054 0
        for i in range(0, nLinks):
1055 0
            links.append(obj.hypertext.getLink(i))
1056
1057 0
        for i in range(0, len(links)):
1058 0
            if links[i].endIndex > startOffset and \
1059
               links[i].endIndex <= endOffset:
1060 0
                return True
1061
1062 0
        return False
1063
1064 1
    def sayWriterWord(self, obj, word, startOffset, endOffset):
1065
        """Speaks the given word in the appropriate voice. If this word is
1066
        a hypertext link and it is also at the end offset for one of the
1067
        links, then the word "link" is also spoken.
1068
1069
        Arguments:
1070
        - obj: an Accessible object that implements the AccessibleText
1071
               interface
1072
        - word: the word to speak
1073
        - startOffset: the start offset for this word
1074
        - endOffset: the end offset for this word
1075
        """
1076
1077 0
        voices = settings.voices
1078
1079 0
        for i in range(startOffset, endOffset):
1080 0
            if self.getLinkIndex(obj, i) >= 0:
1081 0
                voice = voices[settings.HYPERLINK_VOICE]
1082 0
                break
1083 0
            elif word.isupper():
1084 0
                voice = voices[settings.UPPERCASE_VOICE]
1085
            else:
1086 0
                voice = voices[settings.DEFAULT_VOICE]
1087
1088 0
        speech.speak(word, voice)
1089 0
        if self.endOfLink(obj, word, startOffset, endOffset):
1090 0
            speech.speak(_("link"))
1091
1092 1
    def isSetupDialog(self, obj):
1093
        """ Check to see if this object is in the Setup dialog by walking
1094
        back up the object hierarchy until we get to the dialog object and
1095
        checking to see if it has a name that starts with "Welcome to
1096
        StarOffice".
1097
1098
        Arguments:
1099
        - obj: an Accessible object that implements the AccessibleText
1100
               interface
1101
1102
        Returns an indication of whether this object is in the Setup dialog.
1103
        """
1104
1105 138
        found = False
1106 846
        while obj and obj.role != rolenames.ROLE_APPLICATION:
1107 708
            if obj.role == rolenames.ROLE_DIALOG and \
1108
                (obj.name and obj.name.startswith(_("Welcome to StarOffice"))):
1109 0
                debug.println(self.debugLevel,
1110 0
                              "StarOffice.isSetupDialog: True.")
1111 0
                found = True
1112
1113 708
            obj = obj.parent
1114
1115 138
        return found
1116
1117 1
    def speakSetupLabel(self, label):
1118
        """Speak this Setup dialog label.
1119
1120
        Arguments:
1121
        - label: the Setup dialog Label.
1122
        """
1123
1124 0
        text = self.getDisplayedText(label)
1125 0
        if text:
1126 0
            speech.speak(text)
1127
1128 1
    def handleSetupPanel(self, panel):
1129
        """Find all the labels in this Setup panel and speak them.
1130
1131
        Arguments:
1132
        - panel: the Setup panel.
1133
        """
1134
1135 0
        allLabels = self.findByRole(panel, rolenames.ROLE_LABEL)
1136 0
        for i in range(0, len(allLabels)):
1137 0
            self.speakSetupLabel(allLabels[i])
1138
1139
    # This method tries to detect and handle the following cases:
1140
    # 0) Writer: find command.
1141
    # 1) Writer: text paragraph.
1142
    # 2) Writer: spell checking dialog.
1143
    # 3) Welcome to StarOffice dialog.
1144
    # 4) Calc: cell editor.
1145
    # 5) Calc: name box.
1146
1147 1
    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
1148
        """Called when the visual object with focus changes.
1149
1150
        Arguments:
1151
        - event: if not None, the Event that caused the change
1152
        - oldLocusOfFocus: Accessible that is the old locus of focus
1153
        - newLocusOfFocus: Accessible that is the new locus of focus
1154
        """
1155
1156 133
        brailleGen = self.brailleGenerator
1157 133
        speechGen = self.speechGenerator
1158
1159 133
        debug.printObjectEvent(self.debugLevel,
1160 133
                               event,
1161 133
                               event.source.toString())
1162
1163
        # self.printAncestry(event.source)
1164
1165
        # 0) Writer: find command
1166
        #
1167
        # Check to see if this is this is for the find command. See 
1168
        # comment #18 of bug #354463.
1169
        #
1170 133
        if self.findCommandRun and \
1171
           event.type == "object:state-changed:focused":
1172 0
            self.findCommandRun = False
1173 0
            self.find()
1174 0
            return
1175
1176
        # 1) Writer: text paragraph.
1177
        #
1178
        # We need to handle two things here:
1179
        #
1180
        # If the old locus of focus was on the File->New->Text Document
1181
        # menu item and we are currently have focus on an empty text
1182
        # paragraph, then we've just created the first new text document
1183
        # in Writer. Announce it by doing a "where am I".
1184
        #
1185
        # Also, when the focus is on a paragraph in the Document view of
1186
        # the Writer, then just speak/braille the current line (rather than
1187
        # speaking a bogus initial "paragraph" utterance as well).
1188
1189 133
        rolesList = [rolenames.ROLE_PARAGRAPH, \
1190
                     rolenames.ROLE_UNKNOWN, \
1191
                     rolenames.ROLE_SCROLL_PANE, \
1192
                     rolenames.ROLE_PANEL, \
1193
                     rolenames.ROLE_ROOT_PANE, \
1194
                     rolenames.ROLE_FRAME]
1195 133
        if self.isDesiredFocusedItem(event.source, rolesList):
1196 2
            debug.println(self.debugLevel,
1197 2
                  "StarOffice.locusOfFocusChanged - Writer: text paragraph.")
1198
1199 2
            result = self.getTextLineAtCaret(event.source)
1200 2
            result[0] = result[0].decode("UTF-8")
1201
1202 2
            if oldLocusOfFocus.role == rolenames.ROLE_MENU_ITEM and \
1203
               oldLocusOfFocus.name == _("Text Document") and \
1204
               len(result[0]) == 0:
1205 0
                self.whereAmI(None)
1206
1207
            # Check to see if there are any hypertext links in this paragraph.
1208
            # If no, then just speak the whole line. Otherwise, split the
1209
            # line into words and call sayWriterWord() to speak that token
1210
            # in the appropriate voice.
1211
            #
1212 2
            hypertext = event.source.hypertext
1213 2
            if not hypertext or (hypertext.getNLinks() == 0):
1214 2
                if settings.enableSpeechIndentation:
1215 0
                    self.speakTextIndentation(event.source,
1216 0
                                              result[0].encode("UTF-8"))
1217 2
                speech.speak(result[0].encode("UTF-8"), None, False)
1218
            else:
1219 0
                started = False
1220 0
                startOffset = 0
1221 0
                for i in range(0, len(result[0])):
1222 0
                    if result[0][i] == ' ':
1223 0
                        if started:
1224 0
                            endOffset = i
1225 0
                            self.sayWriterWord(event.source,
1226 0
                                result[0][startOffset:endOffset+1].encode("UTF-8"),
1227 0
                                startOffset, endOffset)
1228 0
                            startOffset = i
1229 0
                            started = False
1230
                    else:
1231 0
                        if not started:
1232 0
                            startOffset = i
1233 0
                            started = True
1234
1235 0
                if started:
1236 0
                    endOffset = len(result[0])
1237 0
                    self.sayWriterWord(event.source,
1238 0
                        result[0][startOffset:endOffset].encode("UTF-8"),
1239 0
                        startOffset, endOffset)
1240
1241 2
            braille.displayRegions(brailleGen.getBrailleRegions(event.source))
1242
1243 2
            return
1244
1245
        # 2) Writer: spell checking dialog.
1246
        #
1247
        # Check to see if the Spell Check dialog has just appeared and got
1248
        # focus. If it has, then speak/braille the current misspelt word
1249
        # plus its context.
1250
        #
1251
        # Note that in order to make sure that this focus event is for the
1252
        # spell check dialog, a check is made of the localized name of the
1253
        # option pane. Translators for other locales will need to ensure that
1254
        # their translation of this string matches what StarOffice uses in
1255
        # that locale.
1256
1257 131
        rolesList = [rolenames.ROLE_PUSH_BUTTON, \
1258
                     rolenames.ROLE_OPTION_PANE, \
1259
                     rolenames.ROLE_DIALOG, \
1260
                     rolenames.ROLE_APPLICATION]
1261 131
        if self.isDesiredFocusedItem(event.source, rolesList):
1262 0
            pane = event.source.parent
1263 0
            if pane.name.startswith(_("Spellcheck:")):
1264 0
                debug.println(self.debugLevel,
1265 0
                    "StarOffice.locusOfFocusChanged - " \
1266
                    + "Writer: spell check dialog.")
1267
1268 0
                self.readMisspeltWord(event, pane)
1269
1270
                # Fall-thru to process the event with the default handler.
1271
1272
        # 3) Welcome to StarOffice dialog.
1273
        #
1274
        # Check to see if the object that just got focus is in the Setup
1275
        # dialog. If it is, then check for a variety of scenerios.
1276
1277 131
        if self.isSetupDialog(event.source):
1278
1279
            # Check for 2. License Agreement: Scroll Down button.
1280
            #
1281 0
            rolesList = [rolenames.ROLE_PUSH_BUTTON, \
1282
                         rolenames.ROLE_PANEL, \
1283
                         rolenames.ROLE_OPTION_PANE, \
1284
                         rolenames.ROLE_DIALOG, \
1285
                         rolenames.ROLE_APPLICATION]
1286 0
            if self.isDesiredFocusedItem(event.source, rolesList):
1287 0
                debug.println(self.debugLevel,
1288 0
                    "StarOffice.locusOfFocusChanged - Setup dialog: " \
1289
                    + "License Agreement screen: Scroll Down button.")
1290 0
                self.handleSetupPanel(event.source.parent)
1291 0
                speech.speak(_("Note that the Scroll Down button has to be pressed numerous times."))
1292
1293
            # Check for 2. License Agreement: Accept button.
1294
            #
1295 0
            rolesList = [rolenames.ROLE_UNKNOWN, \
1296
                         rolenames.ROLE_SCROLL_PANE, \
1297
                         rolenames.ROLE_PANEL, \
1298
                         rolenames.ROLE_OPTION_PANE, \
1299
                         rolenames.ROLE_DIALOG, \
1300
                         rolenames.ROLE_APPLICATION]
1301 0
            if self.isDesiredFocusedItem(event.source, rolesList):
1302 0
                debug.println(self.debugLevel,
1303 0
                    "StarOffice.locusOfFocusChanged - Setup dialog: " \
1304
                    + "License Agreement screen: accept button.")
1305 0
                speech.speak(_("License Agreement Accept button now has focus."))
1306
1307
            # Check for 3. Personal Data: Transfer Personal Data check box.
1308
            #
1309 0
            rolesList = [rolenames.ROLE_CHECK_BOX, \
1310
                         rolenames.ROLE_PANEL, \
1311
                         rolenames.ROLE_OPTION_PANE, \
1312
                         rolenames.ROLE_DIALOG, \
1313
                         rolenames.ROLE_APPLICATION]
1314 0
            if self.isDesiredFocusedItem(event.source, rolesList):
1315 0
                debug.println(self.debugLevel,
1316 0
                    "StarOffice.locusOfFocusChanged - Setup dialog: " \
1317
                    + "Personal Data: Transfer Personal Data check box.")
1318 0
                self.handleSetupPanel(event.source.parent)
1319
1320
            # Check for 4. User name: First Name text field.
1321
            #
1322 0
            rolesList = [rolenames.ROLE_TEXT, \
1323
                        rolenames.ROLE_PANEL, \
1324
                        rolenames.ROLE_OPTION_PANE, \
1325
                        rolenames.ROLE_DIALOG, \
1326
                        rolenames.ROLE_APPLICATION]
1327 0
            if self.isDesiredFocusedItem(event.source, rolesList) and \
1328
               event.source.name == _("First name"):
1329 0
                debug.println(self.debugLevel,
1330 0
                    "StarOffice.locusOfFocusChanged - Setup dialog: " \
1331
                    + "User name: First Name text field.")
1332
1333
                # Just speak the informative labels at the top of the panel
1334
                # (and not the ones that have LABEL_FOR relationships).
1335
                #
1336 0
                panel = event.source.parent
1337 0
                allLabels = self.findByRole(panel, rolenames.ROLE_LABEL)
1338 0
                for i in range(0, len(allLabels)):
1339 0
                    relations = allLabels[i].relations
1340 0
                    hasLabelFor = False
1341 0
                    for relation in relations:
1342 0
                        if relation.getRelationType() \
1343
                               == atspi.Accessibility.RELATION_LABEL_FOR:
1344 0
                            hasLabelFor = True
1345 0
                    if not hasLabelFor:
1346 0
                        self.speakSetupLabel(allLabels[i])
1347
1348
            # Check for 5. Registration: Register Now radio button.
1349
            #
1350 0
            rolesList = [rolenames.ROLE_RADIO_BUTTON, \
1351
                        rolenames.ROLE_PANEL, \
1352
                        rolenames.ROLE_OPTION_PANE, \
1353
                        rolenames.ROLE_DIALOG, \
1354
                        rolenames.ROLE_APPLICATION]
1355 0
            if self.isDesiredFocusedItem(event.source, rolesList):
1356 0
                debug.println(self.debugLevel,
1357 0
                    "StarOffice.locusOfFocusChanged - Setup dialog: " \
1358
                    + "Registration: Register Now radio button.")
1359 0
                self.handleSetupPanel(event.source.parent)
1360
1361
        # 4) Calc: cell editor.
1362
        #
1363
        # Check to see if we are editing a spread sheet cell. If so, just
1364
        # return to avoid uttering something like "Paragraph 0 paragraph".
1365
        #
1366 131
        rolesList = [rolenames.ROLE_PARAGRAPH, \
1367
                     rolenames.ROLE_PANEL, \
1368
                     rolenames.ROLE_UNKNOWN, \
1369
                     rolenames.ROLE_SCROLL_PANE, \
1370
                     rolenames.ROLE_PANEL, \
1371
                     rolenames.ROLE_ROOT_PANE, \
1372
                     rolenames.ROLE_FRAME, \
1373
                     rolenames.ROLE_APPLICATION]
1374 131
        if self.isDesiredFocusedItem(event.source, rolesList):
1375 0
            debug.println(self.debugLevel, "StarOffice.locusOfFocusChanged - " \
1376
                          + "Calc: cell editor.")
1377 0
            return
1378
1379
        # 5) Calc: name box
1380
        #
1381
        # Check to see if the focus has just moved to the Name Box combo
1382
        # box in Calc. If so, then replace the non-existent name with a
1383
        # simple one before falling through and calling the default 
1384
        # locusOfFocusChanged method, which in turn will result in our
1385
        # _getSpeechForComboBox() method being called.
1386
        #
1387 131
        rolesList = [rolenames.ROLE_COMBO_BOX, \
1388
                     rolenames.ROLE_TOOL_BAR, \
1389
                     rolenames.ROLE_PANEL, \
1390
                     rolenames.ROLE_ROOT_PANE, \
1391
                     rolenames.ROLE_FRAME, \
1392
                     rolenames.ROLE_APPLICATION]
1393
1394 131
        if self.isDesiredFocusedItem(event.source, rolesList) \
1395
            and (not event.source.name or len(event.source.name) == 0):
1396 0
            debug.println(self.debugLevel, "StarOffice.locusOfFocusChanged - " \
1397
                          + "Calc: name box.")
1398
1399 0
            event.source.name = _("Move to cell")
1400
1401
        # Pass the event onto the parent class to be handled in the default way.
1402
1403 131
        default.Script.locusOfFocusChanged(self, event,
1404 131
                                           oldLocusOfFocus, newLocusOfFocus)
1405
1406
    # This method tries to detect and handle the following cases:
1407
    # 1) Setup dialog.
1408
1409 1
    def onWindowActivated(self, event):
1410
        """Called whenever a property on an object changes.
1411
1412
        Arguments:
1413
        - event: the Event
1414
        """
1415
1416 7
        debug.printObjectEvent(self.debugLevel,
1417 7
                               event,
1418 7
                               event.source.toString())
1419
1420
        # self.printAncestry(event.source)
1421
1422
        # 1) Setup dialog.
1423
        #
1424
        # Check to see if the Setup dialog window has just been activated.
1425
        # If it has, then find the panel within it that has no name and
1426
        # speak all the labels within that panel.
1427
        #
1428 7
        if self.isSetupDialog(event.source):
1429 0
            debug.println(self.debugLevel,
1430 0
                "StarOffice.onWindowActivated - Setup dialog: Welcome screen.")
1431
1432 0
            allPanels = self.findByRole(event.source.parent,
1433 0
                                        rolenames.ROLE_PANEL)
1434 0
            for i in range(0, len(allPanels)):
1435 0
                if not allPanels[i].name:
1436 0
                    allLabels = self.findByRole(allPanels[i],
1437 0
                                                rolenames.ROLE_LABEL)
1438 0
                    for i in range(0, len(allLabels)):
1439 0
                        self.speakSetupLabel(allLabels[i])
1440
        else:
1441
            # Pass the event onto the parent class to be handled in the
1442
            # default way.
1443
            #
1444 7
            default.Script.onWindowActivated(self, event)
1445
1446
1447
    # This method tries to detect and handle the following cases:
1448
    # 1) Writer: spell checking dialog.
1449
1450 1
    def onNameChanged(self, event):
1451
        """Called whenever a property on an object changes.
1452
1453
        Arguments:
1454
        - event: the Event
1455
        """
1456
1457 42
        brailleGen = self.brailleGenerator
1458 42
        speechGen = self.speechGenerator
1459
1460 42
        debug.printObjectEvent(self.debugLevel,
1461 42
                               event,
1462 42
                               event.source.toString())
1463
1464
        # self.printAncestry(event.source)
1465
1466
        # 1) Writer: spell checking dialog.
1467
        #
1468
        # Check to see if if we've had a property-change event for the
1469
        # accessible name for the option pane in the spell check dialog.
1470
        # This (hopefully) means that the user has just corrected a
1471
        # spelling mistake, in which case, speak/braille the current
1472
        # misspelt word plus its context.
1473
        #
1474
        # Note that in order to make sure that this focus event is for the
1475
        # spell check dialog, a check is made of the localized name of the
1476
        # option pane. Translators for other locales will need to ensure that
1477
        # their translation of this string matches what StarOffice uses in
1478
        # that locale.
1479
1480 42
        rolesList = [rolenames.ROLE_OPTION_PANE, \
1481
                     rolenames.ROLE_DIALOG, \
1482
                     rolenames.ROLE_APPLICATION]
1483 42
        if self.isDesiredFocusedItem(event.source, rolesList):
1484 0
            pane = event.source
1485 0
            if pane.name.startswith(_("Spellcheck:")):
1486 0
                debug.println(self.debugLevel,
1487 0
                      "StarOffice.onNameChanged - Writer: spell check dialog.")
1488
1489 0
                self.readMisspeltWord(event, pane)
1490
1491
                # Fall-thru to process the event with the default handler.
1492
1493
        # Pass the event onto the parent class to be handled in the default way.
1494
1495 42
        default.Script.onNameChanged(self, event)
1496
1497
1498 1
    def onFocus(self, event):
1499
        """Called whenever an object gets focus. Overridden in this script 
1500
        so that we can adjust "focus:" events for children of a combo-box 
1501
        to just set the focus to the combo box. This is needed to help 
1502
        reduce the verbosity of focusing on the Calc Name combo box (see 
1503
        bug #364407).
1504
1505
        Arguments:
1506
        - event: the Event
1507
        """
1508
1509 155
        if event.source.parent.role == rolenames.ROLE_COMBO_BOX:
1510 0
            orca.setLocusOfFocus(None, event.source.parent, False)
1511 0
            return
1512
1513
        # If we are FOCUSED on a paragraph inside a table cell (in Writer),
1514
        # then speak/braille that parent table cell (see bug #382415).
1515
        #
1516 155
        if event.source.role == rolenames.ROLE_PARAGRAPH and \
1517
           event.source.parent.role == rolenames.ROLE_TABLE_CELL and \
1518
           event.source.state.count(atspi.Accessibility.STATE_FOCUSED):
1519 0
            if self.lastCell != event.source.parent:
1520 0
                default.Script.locusOfFocusChanged(self, event,
1521 0
                                                   None, event.source.parent)
1522 0
                self.lastCell = event.source.parent
1523 0
            return
1524
1525 155
        default.Script.onFocus(self, event)
1526
1527 1
    def onStateChanged(self, event):
1528
        """Called whenever an object's state changes.
1529
1530
        Arguments:
1531
        - event: the Event
1532
        """
1533
1534
        # If this is a state change "focused" event that we care about, and 
1535
        # we are in Writer, check to see if we are entering or leaving a table.
1536
        #
1537 563
        if event.type == "object:state-changed:focused" and event.detail1 == 1:
1538 63
            current = event.source.parent
1539 317
            while current.role != rolenames.ROLE_APPLICATION:
1540 301
                if current.role == rolenames.ROLE_FRAME and \
1541
                   (current.name and current.name.endswith(_("Writer"))):
1542 47
                    self.checkForTableBoundry(orca_state.locusOfFocus, 
1543 47
                                              event.source)
1544 47
                    break
1545 254
                current = current.parent
1546
1547
        # Prevent  "object:state-changed:active" events from activating
1548
        # the find operation. See comment #18 of bug #354463.
1549
        #
1550 551
        if event.type == "object:state-changed:active":
1551 23
            if self.findCommandRun:
1552 0
                return
1553
1554
            # [[[TODO: JD - HACK because we won't get events from toggle
1555
            # buttons on the Formatting toolbar until we "tickle/poke"
1556
            # the hierarchy. But we only want to do it once.
1557
            # See bug #363830 and OOo issue #70872.]]]
1558
            #
1559 23
            if not self.tickled:
1560 4
                self.tickled = self.flatReviewContextClass(self)
1561
1562
        # Announce when the toolbar buttons are toggled if we just toggled
1563
        # them; not if we navigated to some text.
1564
        #
1565 551
        if event.type == "object:state-changed:checked" and \
1566
           (event.source.role == rolenames.ROLE_TOGGLE_BUTTON or \
1567
            event.source.role == rolenames.ROLE_PUSH_BUTTON):
1568 0
            weToggledIt = False
1569 0
            if isinstance(orca_state.lastInputEvent, \
1570 0
                          input_event.MouseButtonEvent):
1571 0
                x = orca_state.lastInputEvent.x
1572 0
                y = orca_state.lastInputEvent.y
1573 0
                weToggledIt = event.source.component.contains(x, y, 0)
1574
1575 0
            elif isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
1576 0
                keyString = orca_state.lastInputEvent.event_string  
1577 0
                navKeys = ["Up", "Down", "Page_Up", "Page_Down", "Home", "End"]
1578 0
                wasCommand = orca_state.lastInputEvent.modifiers \
1579
                             & (1 << atspi.Accessibility.MODIFIER_CONTROL \
1580
                              | 1 << atspi.Accessibility.MODIFIER_ALT \
1581
                              | 1 << atspi.Accessibility.MODIFIER_META \
1582
                              | 1 << atspi.Accessibility.MODIFIER_META2 \
1583
                              | 1 << atspi.Accessibility.MODIFIER_META3)
1584 0
                weToggledIt = wasCommand and keyString not in navKeys
1585
1586 0
            if weToggledIt:
1587 0
                speech.speakUtterances(self.speechGenerator.getSpeech( \
1588
                                       event.source, False))
1589
1590
        # If we are FOCUSED on a paragraph inside a table cell (in Writer),
1591
        # then speak/braille that parent table cell (see bug #382415).
1592
        #
1593 551
        if event.type == "object:state-changed:focused" and \
1594
           event.source.role == rolenames.ROLE_PARAGRAPH and \
1595
           event.source.parent.role == rolenames.ROLE_TABLE_CELL and \
1596
           event.detail1 == 0 and \
1597
           event.source.state.count(atspi.Accessibility.STATE_FOCUSED):
1598
1599
            # Check to see if the last input event was "Up" or "Down".
1600
            # If it was, and we are in the same table cell as last time,
1601
            # and if that table cell has more than one child, then just
1602
            # get the speech for that single child, otherwise speak/braille
1603
            # the parent table cell.
1604
            #
1605 0
            event_string = None
1606 0
            if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
1607 0
                event_string = orca_state.lastInputEvent.event_string
1608 0
            if (event_string == "Up" or event_string == "Down") and \
1609
               event.source.parent == self.lastCell and \
1610
               event.source.parent.childCount > 1:
1611 0
                default.Script.locusOfFocusChanged(self, event,
1612 0
                                                   None, event.source)
1613
            else:
1614 0
                default.Script.locusOfFocusChanged(self, event,
1615 0
                                                   None, event.source.parent)
1616 0
            self.lastCell = event.source.parent
1617 0
            return
1618
1619
        # Two events are received when the caret moves
1620
        # to a new paragraph. The first is a focus event
1621
        # (in the form of object:state-changed:focused
1622
        # instead of focus:). The second is a caret-moved
1623
        # event. Just set the locusOfFocus for the first event.
1624
        # 
1625 551
        if event.type == "object:state-changed:focused" and \
1626
           event.source.role == rolenames.ROLE_PARAGRAPH and \
1627
           event.source != self.currentParagraph:
1628 38
            self.currentParagraph = event.source
1629 38
            orca.setLocusOfFocus(event, event.source, False)
1630 38
            return
1631
1632
        # If we get "object:state-changed:focused" events for children of 
1633
        # a combo-box, just set the focus to the combo box. This is needed 
1634
        # to help reduce the verbosity of focusing on the Calc Name combo 
1635
        # box (see bug #364407).
1636
        #
1637 513
        if event.source.parent and \
1638
           event.source.parent.role == rolenames.ROLE_COMBO_BOX:
1639 1
            orca.setLocusOfFocus(None, event.source.parent, False)
1640 1
            return
1641
1642 512
        default.Script.onStateChanged(self, event)
1643
1644
1645
    # This method tries to detect and handle the following cases:
1646
    # 1) Calc: spread sheet Name Box line.
1647
1648 1
    def onSelectionChanged(self, event):
1649
        """Called when an object's selection changes.
1650
1651
        Arguments:
1652
        - event: the Event
1653
        """
1654
1655 33
        debug.printObjectEvent(self.debugLevel,
1656 33
                               event,
1657 33
                               event.source.toString())
1658
1659
        # self.printAncestry(event.source)
1660
1661
        # 1) Calc: spread sheet input line.
1662
        #
1663
        # If this "object:selection-changed" is for the spread sheet Name
1664
        # Box, then check to see if the current locus of focus is a spread
1665
        # sheet cell. If it is, and the contents of the input line are
1666
        # different from what is displayed in that cell, then speak "has
1667
        # formula" and append it to the braille line.
1668
        #
1669 33
        rolesList = [rolenames.ROLE_LIST, \
1670
                     rolenames.ROLE_COMBO_BOX, \
1671
                     rolenames.ROLE_PANEL, \
1672
                     rolenames.ROLE_TOOL_BAR, \
1673
                     rolenames.ROLE_PANEL, \
1674
                     rolenames.ROLE_ROOT_PANE, \
1675
                     rolenames.ROLE_FRAME, \
1676
                     rolenames.ROLE_APPLICATION]
1677 33
        if self.isDesiredFocusedItem(event.source, rolesList):
1678 0
            if orca_state.locusOfFocus.role == rolenames.ROLE_TABLE_CELL:
1679 0
                cell = orca_state.locusOfFocus
1680
1681
                # We are getting two "object:selection-changed" events
1682
                # for each spread sheet cell move, so in order to prevent
1683
                # appending "has formula" twice, we only do it if the last
1684
                # cell is different from this one.
1685
                #
1686 0
                if cell != self.lastCell:
1687 0
                    self.lastCell = cell
1688
1689 0
                    if cell.text:
1690 0
                        cellText = self.getText(cell, 0, -1)
1691 0
                        if cellText and len(cellText):
1692 0
                            if inputLineForCell and inputLineForCell.text:
1693 0
                                inputLine = self.getText(inputLineForCell,0,-1)
1694 0
                                if inputLine and \
1695
                                    (len(inputLine) > 1) and \
1696
                                    (inputLine[0] == "="):
1697 0
                                        hf = _(" has formula")
1698 0
                                        speech.speak(hf, None, False)
1699
1700 0
                                        line = braille.getShowingLine()
1701 0
                                        line.addRegion(braille.Region(hf))
1702 0
                                        braille.refresh()
1703
                                        #
1704
                                        # Fall-thru to process the event with
1705
                                        # the default handler.
1706
1707 33
        default.Script.onSelectionChanged(self, event)
1708
1709 1
    def getText(self, obj, startOffset, endOffset):
1710
        """Returns the substring of the given object's text specialization.
1711
1712
        NOTE: This is here to handle the problematic implementation of
1713
        getText by OpenOffice.  See the bug discussion at:
1714
1715
           http://bugzilla.gnome.org/show_bug.cgi?id=356425)
1716
1717
        Once the OpenOffice issue has been resolved, this method probably
1718
        should be removed.
1719
1720
        Arguments:
1721
        - obj: an accessible supporting the accessible text specialization
1722
        - startOffset: the starting character position
1723
        - endOffset: the ending character position
1724
        """
1725 59
        text = obj.text.getText(0, -1).decode("UTF-8")
1726 59
        if startOffset >= len(text):
1727 0
            startOffset = len(text) - 1
1728 59
        if endOffset == -1:
1729 12
            endOffset = len(text)
1730 47
        elif startOffset >= endOffset:
1731 0
            endOffset = startOffset + 1
1732 59
        string = text[max(0,startOffset):min(len(text),endOffset)]
1733 59
        string = string.encode("UTF-8")
1734 59
        return string
1735
1736 1
    def speakCellName(self, name):
1737
        """Speaks the given cell name.
1738
1739
        Arguments:
1740
        - name: the name of the cell
1741
        """
1742
1743 0
        line = _("Cell ") + name
1744 0
        speech.speak(line)
1745
1746 1
    def onCaretMoved(self, event):
1747
        """Called whenever the caret moves.
1748
1749
        Arguments:
1750
        - event: the Event
1751
        """
1752
1753
        # If we are FOCUSED on a paragraph inside a table cell (in Writer),
1754
        # then just return (modulo the special cases below). Speaking and 
1755
        # brailling will have been done in the onStateChanged() routine 
1756
        # (see bug #382415).
1757
        #
1758 196
        if event.source.role == rolenames.ROLE_PARAGRAPH and \
1759
           event.source.parent.role == rolenames.ROLE_TABLE_CELL and \
1760
           event.source.state.count(atspi.Accessibility.STATE_FOCUSED):
1761 0
            event_string = orca_state.lastInputEvent.event_string
1762
1763
            # If we are moving up and down, and we are speaking-by-cell
1764
            # (as opposed to by-row), then speak the cell name. Otherwise
1765
            # just return.
1766
            #
1767 0
            if (event_string == "Up" or event_string == "Down"):
1768 0
                if settings.readTableCellRow == False:
1769 0
                    if event.detail1 != -1:
1770 0
                        self.speakCellName(event.source.parent.name)
1771 0
                return
1772
1773
            # If we are moving left or right and we are in a new cell, just
1774
            # return.
1775
            #
1776 0
            if (event_string == "Left" or event_string == "Right") and \
1777
               self.lastCell != event.source.parent:
1778 0
                return
1779
1780 0
            caretOffset = event.source.text.caretOffset
1781 0
            len = event.source.text.characterCount
1782
1783
            # If you are in a table cell and you arrow Right, the caret 
1784
            # will focus at the end of the current paragraph before moving 
1785
            # into the next cell. To be similar to the way that caret 
1786
            # navigation works in other paragraphs in OOo, just return.
1787
            #
1788 0
            if event_string == "Right" and caretOffset == len:
1789 0
                return
1790
1791
            # If we have moved left and the caret position is at the end of
1792
            # the paragraph or if we have moved right and the caret position
1793
            # is at the start of the text string, or the last key input was
1794
            # Tab or Shift-Tab, and if we are speaking-by-cell (as opposed 
1795
            # to by-row), then speak the cell name, otherwise just return 
1796
            # (see bug #382418).
1797
            #
1798 0
            if (event_string == "Left" and caretOffset == len) or \
1799
               (event_string == "Right" and caretOffset == 0) or \
1800
               (event_string == "Tab" or event_string == "ISO_Left_Tab"):
1801 0
                if settings.readTableCellRow == False:
1802 0
                    if event.detail1 != -1:
1803 0
                        self.speakCellName(event.source.parent.name)
1804
1805
                # Speak a blank line, if appropriate.
1806 0
                if self.speakBlankLine(event.source):
1807 0
                    speech.speak(_("blank"), None, False)
1808 0
                return
1809
1810
        # Speak a newline, if appropriate.
1811 196
        if self.speakNewLine(event.source):
1812 12
            speech.speak(chnames.getCharacterName("\n"), None, False)
1813
1814
        # Speak a blank line, if appropriate.
1815 196
        if self.speakBlankLine(event.source):
1816 10
            speech.speak(_("blank"), None, False)
1817
1818 196
        default.Script.onCaretMoved(self, event)
1819
1820
1821 1
    def speakNewLine(self, obj):
1822
        """Returns True if a newline should be spoken.
1823
           Otherwise, returns False.
1824
        """
1825
1826
        # Get the the AccessibleText interrface.
1827 196
        text = obj.text
1828 196
        if not text:
1829 0
            return False
1830
1831
        # Was a left or right-arrow key pressed?
1832 196
        if not (orca_state.lastInputEvent and \
1833
                orca_state.lastInputEvent.__dict__.has_key("event_string")):
1834 0
            return False
1835
1836 196
        lastKey = orca_state.lastInputEvent.event_string
1837 196
        if lastKey != "Left" and lastKey != "Right":
1838 160
            return False
1839
1840
        # Was a control key pressed?
1841 36
        mods = orca_state.lastInputEvent.modifiers
1842 36
        isControlKey = mods & (1 << atspi.Accessibility.MODIFIER_CONTROL)
1843
1844
        # Get the line containing the caret
1845 36
        caretOffset = text.caretOffset
1846 36
        line = text.getTextAtOffset(caretOffset, \
1847 36
            atspi.Accessibility.TEXT_BOUNDARY_LINE_START)
1848 36
        lineStart = line[1]
1849 36
        lineEnd = line[2]
1850
1851 36
        if isControlKey:  # control-right-arrow or control-left-arrow
1852
1853
            # Get the word containing the caret.
1854 0
            word = text.getTextAtOffset(caretOffset, \
1855 0
                atspi.Accessibility.TEXT_BOUNDARY_WORD_START)
1856 0
            wordStart = word[1]
1857 0
            wordEnd = word[2]
1858
1859 0
            if lastKey == "Right":
1860 0
                if wordStart == lineStart:
1861 0
                    return True
1862
            else: 
1863 0
                if wordEnd == lineEnd:
1864 0
                    return True
1865
1866
        else:  # right arrow or left arrow
1867
1868 36
            if lastKey == "Right":
1869 18
                if caretOffset == lineStart:
1870 6
                    return True
1871
            else: 
1872 18
                if caretOffset == lineEnd:
1873 6
                    return True
1874
1875 24
        return False
1876
1877
1878 1
    def speakBlankLine(self, obj):
1879
        """Returns True if a blank line should be spoken.
1880
        Otherwise, returns False.
1881
        """
1882
1883
        # Get the the AccessibleText interrface. 
1884 196
        text = obj.text
1885 196
        if not text:
1886 0
            return False
1887
1888
        # Get the line containing the caret
1889 196
        caretOffset = text.caretOffset
1890 196
        line = text.getTextAtOffset(caretOffset, \
1891 196
            atspi.Accessibility.TEXT_BOUNDARY_LINE_START)
1892
1893
        # If this is a blank line, announce it if the user requested
1894
        # that blank lines be spoken.
1895 196
        if line[1] == 0 and line[2] == 0:
1896 10
            return settings.speakBlankLines
1897