~ubuntu-branches/ubuntu/wily/gnome-orca/wily-proposed

« back to all changes in this revision

Viewing changes to src/orca/scripts/apps/soffice/script.py

  • Committer: Package Import Robot
  • Author(s): Mario Lang, Emilio Pozuelo Monfort, Mario Lang
  • Date: 2014-03-26 09:02:03 UTC
  • mfrom: (1.1.19)
  • Revision ID: package-import@ubuntu.com-20140326090203-hklufxw4me5mq70b
Tags: 3.12.0-1
[ Emilio Pozuelo Monfort ]
* debian/control.in:
  + Depend on gsettings-desktop-schemas, needed for the a11y gsettings
    keys. Closes: #741211.

[ Mario Lang ]
* New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Orca
2
2
#
3
3
# Copyright 2005-2009 Sun Microsystems Inc.
 
4
# Copyright 2010-2013 The Orca Team.
4
5
#
5
6
# This library is free software; you can redistribute it and/or
6
7
# modify it under the terms of the GNU Lesser General Public
17
18
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
18
19
# Boston MA  02110-1301 USA.
19
20
 
20
 
# [[[TODO: JD - Pylint is giving us a number of errors along these
21
 
# lines throughout this file:
22
 
#
23
 
# E1103:690:Script.presentTableInfo: Instance of 'list' has no
24
 
# 'queryTable' member (but some types could not be inferred)
25
 
#
26
 
# In each case, we're not querying the table interface (or asking
27
 
# for the name) of a list, but rather of an accessible. Pylint is
28
 
# correct about it's suggestion that it cannot infer types.]]]
29
 
#
30
 
# pylint: disable-msg=E1103
31
 
 
32
 
"""Custom script for StarOffice and OpenOffice."""
 
21
"""Custom script for LibreOffice."""
33
22
 
34
23
__id__        = "$Id$"
35
24
__version__   = "$Revision$"
36
25
__date__      = "$Date$"
37
 
__copyright__ = "Copyright (c) 2005-2009 Sun Microsystems Inc."
 
26
__copyright__ = "Copyright (c) 2005-2009 Sun Microsystems Inc." \
 
27
                "Copyright (c) 2010-2013 The Orca Team."
38
28
__license__   = "LGPL"
39
29
 
40
30
from gi.repository import Gtk
73
63
 
74
64
        default.Script.__init__(self, app)
75
65
 
76
 
        # Initialize variable to None to make pylint happy.
77
 
        #
78
66
        self.savedEnabledBrailledTextAttributes = None
79
67
        self.savedEnabledSpokenTextAttributes = None
80
68
        self.speakSpreadsheetCoordinatesCheckButton = None
84
72
        self.speakCellHeadersCheckButton = None
85
73
        self.speakCellSpanCheckButton = None
86
74
 
87
 
        # Set the debug level for all the methods in this script.
88
 
        #
89
 
        self.debugLevel = debug.LEVEL_FINEST
90
 
 
91
 
        # A handle to the last Writer table or Calc spread sheet cell
92
 
        # encountered and its caret offset.
93
 
        #
94
 
        self.lastCell = [None, -1]
95
 
 
96
75
        # The spreadsheet input line.
97
76
        #
98
77
        self.inputLineForCell = None
99
78
 
100
 
        # Dictionaries for the calc dynamic row and column headers.
 
79
        # Dictionaries for the calc and writer dynamic row and column headers.
101
80
        #
102
81
        self.dynamicColumnHeaders = {}
103
82
        self.dynamicRowHeaders = {}
111
90
        self.lastStartOff = -1
112
91
        self.lastEndOff = -1
113
92
 
114
 
        # Used to determine whether the caret has moved to a new paragraph.
115
 
        #
116
 
        self.currentParagraph = None
117
 
 
118
93
    def activate(self):
119
94
        """Called when this script is activated."""
120
95
        self.savedreadTableCellRow = \
127
102
        self.savedEnabledSpokenTextAttributes = \
128
103
            _settingsManager.getSetting('enabledSpokenTextAttributes')
129
104
 
130
 
        # Account for the differences in how OOo expresses indent, 
 
105
        # Account for the differences in how OOo expresses indent,
131
106
        # strikethrough, and margins.
132
107
        #
133
108
        attributes = _settingsManager.getSetting('allTextAttributes')
158
133
 
159
134
        default.Script.activate(self)
160
135
 
161
 
    def getListeners(self):
162
 
        """Sets up the AT-SPI event listeners for this script.
163
 
        """
164
 
        listeners = default.Script.getListeners(self)
165
 
 
166
 
        listeners["object:state-changed:focused"]           = \
167
 
            self.onStateChanged
168
 
        listeners["object:state-changed:sensitive"]         = \
169
 
            self.onStateChanged
170
 
        listeners["object:state-changed:active"]            = \
171
 
            self.onStateChanged
172
 
        listeners["object:state-changed:checked"]           = \
173
 
            self.onStateChanged
174
 
        listeners["object:children-changed"]                = \
175
 
            self.onChildrenChanged
176
 
 
177
 
        return listeners
178
 
 
179
136
    def getBrailleGenerator(self):
180
137
        """Returns the braille generator for this script.
181
138
        """
355
312
            Gtk.CheckButton.new_with_mnemonic(label)
356
313
        self.speakCellHeadersCheckButton.set_active(value)
357
314
        tableGrid.attach(self.speakCellHeadersCheckButton, 0, 2, 1, 1)
358
 
           
 
315
 
359
316
        label = guilabels.TABLE_SKIP_BLANK_CELLS
360
317
        value = _settingsManager.getSetting('skipBlankCells')
361
318
        self.skipBlankCellsCheckButton = \
385
342
                        (prefix, script_settings.speakSpreadsheetCoordinates))
386
343
 
387
344
        value = self.speakCellCoordinatesCheckButton.get_active()
388
 
        _settingsManager.setSetting('speakCellCoordinates', value)            
 
345
        _settingsManager.setSetting('speakCellCoordinates', value)
389
346
        prefs.writelines("orca.settings.speakCellCoordinates = %s\n" % value)
390
347
 
391
348
        value = self.speakCellSpanCheckButton.get_active()
443
400
 
444
401
        return False
445
402
 
446
 
    def adjustForWriterTable(self, obj):
447
 
        """Check to see if we are in Writer, where the object with focus
448
 
        is a paragraph, and the parent is the table cell. If it is, then,
449
 
        return the parent table cell otherwise return the current object.
450
 
 
451
 
        Arguments:
452
 
        - obj: the accessible object to check.
453
 
 
454
 
        Returns parent table cell (if in a Writer table ) or the current
455
 
        object.
456
 
        """
457
 
 
458
 
        if obj.getRole() == pyatspi.ROLE_PARAGRAPH and \
459
 
           obj.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
460
 
            return obj.parent
461
 
        else:
462
 
            return obj
463
 
 
464
 
    def getTable(self, obj):
465
 
        """Get the table that this table cell is in.
466
 
 
467
 
        Arguments:
468
 
        - obj: the table cell.
469
 
 
470
 
        Return the table that this table cell is in, or None if this object
471
 
        isn't in a table.
472
 
        """
473
 
 
474
 
        obj = self.adjustForWriterTable(obj)
475
 
        if obj.getRole() == pyatspi.ROLE_TABLE_CELL and obj.parent:
476
 
            try:
477
 
                obj.parent.queryTable()
478
 
            except NotImplementedError:
479
 
                return None
480
 
 
481
 
        return obj.parent
482
 
 
483
 
    def getDynamicColumnHeaderCell(self, obj, column):
484
 
        """Given a table cell, return the dynamic column header cell
485
 
        associated with it.
486
 
 
487
 
        Arguments:
488
 
        - obj: the table cell.
489
 
        - column: the column that this dynamic header is on.
490
 
 
491
 
        Return the dynamic column header cell associated with the given
492
 
        table cell.
493
 
        """
494
 
 
495
 
        obj = self.adjustForWriterTable(obj)
496
 
        accCell = None
497
 
        parent = obj.parent
498
 
        try:
499
 
            parentTable = parent.queryTable()
500
 
        except NotImplementedError:
501
 
            parentTable = None
502
 
 
503
 
        if parent and parentTable:
504
 
            index = self.utilities.cellIndex(obj)
505
 
            row = parentTable.getRowAtIndex(index)
506
 
            accCell = parentTable.getAccessibleAt(row, column)
507
 
 
508
 
        return accCell
509
 
 
510
 
    def getDynamicRowHeaderCell(self, obj, row):
511
 
        """Given a table cell, return the dynamic row header cell
512
 
        associated with it.
513
 
 
514
 
        Arguments:
515
 
        - obj: the table cell.
516
 
        - row: the row that this dynamic header is on.
517
 
 
518
 
        Return the dynamic row header cell associated with the given
519
 
        table cell.
520
 
        """
521
 
 
522
 
        obj = self.adjustForWriterTable(obj)
523
 
        accCell = None
524
 
        parent = obj.parent
525
 
        try:
526
 
            parentTable = parent.queryTable()
527
 
        except NotImplementedError:
528
 
            parentTable = None
529
 
 
530
 
        if parent and parentTable:
531
 
            index = self.utilities.cellIndex(obj)
532
 
            column = parentTable.getColumnAtIndex(index)
533
 
            accCell = parentTable.getAccessibleAt(row, column)
534
 
 
535
 
        return accCell
536
 
 
537
 
    def locateInputLine(self, obj):
538
 
        """Return the spread sheet input line. This only needs to be found
539
 
        the very first time a spread sheet table cell gets focus. We use the
540
 
        table cell to work back up the component hierarchy until we have found
541
 
        the common panel that both it and the input line reside in. We then
542
 
        use that as the base component to search for a component which has a
543
 
        paragraph role. This will be the input line.
544
 
 
545
 
        Arguments:
546
 
        - obj: the spread sheet table cell that has just got focus.
547
 
 
548
 
        Returns the spread sheet input line component.
549
 
        """
550
 
 
551
 
        inputLine = None
552
 
        panel = obj.parent.parent.parent.parent
553
 
        if panel and panel.getRole() == pyatspi.ROLE_PANEL:
554
 
            allParagraphs = self.utilities.descendantsWithRole(
555
 
                panel, pyatspi.ROLE_PARAGRAPH)
556
 
            if len(allParagraphs) == 1:
557
 
                inputLine = allParagraphs[0]
558
 
            else:
559
 
                debug.println(debug.LEVEL_SEVERE,
560
 
                    "StarOffice: locateInputLine: incorrect paragraph count.")
561
 
        else:
562
 
            debug.println(debug.LEVEL_SEVERE,
563
 
                  "StarOffice: locateInputLine: couldn't find common panel.")
564
 
 
565
 
        return inputLine
566
 
 
567
 
    def getSpreadSheetRowRange(self, obj):
568
 
        """If this is spread sheet cell, return the start and end indices
569
 
        of the spread sheet cells for the table that obj is in. Otherwise
570
 
        return the complete range (0, parentTable.nColumns).
571
 
 
572
 
        Arguments:
573
 
        - obj: a spread sheet table cell.
574
 
 
575
 
        Returns the start and end table cell indices.
576
 
        """
577
 
 
578
 
        parent = obj.parent
579
 
        try:
580
 
            parentTable = parent.queryTable()
581
 
        except NotImplementedError:
582
 
            parentTable = None
583
 
 
584
 
        startIndex = 0
585
 
        endIndex = parentTable.nColumns
586
 
 
587
 
        if self.isSpreadSheetCell(obj):
588
 
            extents = parent.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
589
 
            y = extents.y
590
 
            leftX = extents.x + 1
591
 
            leftCell = \
592
 
                parent.queryComponent().getAccessibleAtPoint(leftX, y, 0)
593
 
            if leftCell:
594
 
                table = leftCell.parent.queryTable()
595
 
                index = self.utilities.cellIndex(leftCell)
596
 
                startIndex = table.getColumnAtIndex(index)
597
 
 
598
 
            rightX = extents.x + extents.width - 1
599
 
            rightCell = \
600
 
                parent.queryComponent().getAccessibleAtPoint(rightX, y, 0)
601
 
            if rightCell:
602
 
                table = rightCell.parent.queryTable()
603
 
                index = self.utilities.cellIndex(rightCell)
604
 
                endIndex = table.getColumnAtIndex(index)
605
 
 
606
 
        return [startIndex, endIndex]
607
 
 
608
 
    def isSpreadSheetCell(self, obj, startFromTable=False):
609
 
        """Return an indication of whether the given obj is a spread sheet
610
 
        table cell.
611
 
 
612
 
        Arguments:
613
 
        - obj: the object to check.
614
 
        - startFromTable: if True, then the component hierarchy check should
615
 
                          start from a table (as opposed to a table cell).
616
 
 
617
 
        Returns True if this is a table cell, False otherwise.
618
 
        """
619
 
 
620
 
        cell = obj
621
 
        if not startFromTable:
622
 
            obj = obj.parent
623
 
 
624
 
        try:
625
 
            table = obj.queryTable()
626
 
        except:
627
 
            # There really doesn't seem to be a good way to identify
628
 
            # when the user is editing a cell because it has a role
629
 
            # of paragraph and no table in the ancestry. This hack is
630
 
            # a carry-over from the whereAmI code.
631
 
            #
632
 
            if cell.getRole() == pyatspi.ROLE_PARAGRAPH:
633
 
                top = self.utilities.topLevelObject(cell)
634
 
                return (top and top.name.endswith(" Calc"))
635
 
            else:
636
 
                return False
637
 
        else:
638
 
            return table.nRows in [65536, 1048576]
639
 
 
640
 
    def presentTableInfo(self, oldFocus, newFocus):
641
 
        """Presents information relevant to a table that was just entered
642
 
        (primarily) or exited.
643
 
 
644
 
        Arguments:
645
 
        - oldFocus: the first accessible to check (usually the previous
646
 
          locusOfFocus)
647
 
        - newFocus: the second accessible to check (usually the current
648
 
          locusOfFocus)
649
 
 
650
 
        Returns True if table info was presented.
651
 
        """
652
 
 
653
 
        oldAncestor = self.utilities.ancestorWithRole(
654
 
            oldFocus,
655
 
            [pyatspi.ROLE_TABLE,
656
 
             pyatspi.ROLE_UNKNOWN,
657
 
             pyatspi.ROLE_DOCUMENT_FRAME],
658
 
            [pyatspi.ROLE_FRAME])
659
 
        newAncestor = self.utilities.ancestorWithRole(
660
 
            newFocus,
661
 
            [pyatspi.ROLE_TABLE,
662
 
             pyatspi.ROLE_UNKNOWN,
663
 
             pyatspi.ROLE_DOCUMENT_FRAME],
664
 
            [pyatspi.ROLE_FRAME])
665
 
 
666
 
        if not (oldAncestor and newAncestor):
667
 
            # At least one of the objects not only is not in a table, but is
668
 
            # is not in a document either.
669
 
            #
670
 
            return False
671
 
        elif self.isSpreadSheetCell(oldAncestor, True) \
672
 
             or self.isSpreadSheetCell(newAncestor, True):
673
 
            # One or both objects is a table in a spreadsheet; we just want
674
 
            # to handle tables in documents (definitely Writer; maybe also
675
 
            # Impress).
676
 
            #
677
 
            return False
678
 
 
679
 
        try:
680
 
            oldTable = oldAncestor.queryTable()
681
 
        except:
682
 
            oldTable = None
683
 
 
684
 
        try:
685
 
            newTable = newAncestor.queryTable()
686
 
        except:
687
 
            newTable = None
688
 
 
689
 
        if oldTable == newTable == None:
690
 
            # We're in a document, but apparently have not entered or left
691
 
            # a table.
692
 
            #
693
 
            return False
694
 
 
695
 
        if not self.utilities.isSameObject(oldAncestor, newAncestor):
696
 
            if oldTable:
697
 
                self.presentMessage(messages.TABLE_LEAVING)
698
 
            if newTable:
699
 
                self.presentMessage(
700
 
                    messages.tableSize(newTable.nRows, newTable.nColumns))
701
 
 
702
 
        if not newTable:
703
 
            self.lastCell = [None, -1]
704
 
            return True
705
 
 
706
 
        cell = self.utilities.ancestorWithRole(
707
 
            newFocus, [pyatspi.ROLE_TABLE_CELL], [pyatspi.ROLE_TABLE])
708
 
        if not cell or self.lastCell[0] == cell:
709
 
            # If we haven't found a cell, who knows what's going on? If
710
 
            # the cell is the same as our last location, odds are that
711
 
            # there are multiple paragraphs in this cell and a focus:
712
 
            # and/or object:state-changed:focused event was emitted.
713
 
            # If we haven't changed cells, we'll just treat this as any
714
 
            # other paragraph and let the caret-moved events do their
715
 
            # thing.
716
 
            #
717
 
            return (cell != None)
718
 
 
719
 
        self.updateBraille(cell)
720
 
        speech.speak(self.speechGenerator.generateSpeech(cell))
721
 
 
722
 
        if not _settingsManager.getSetting('readTableCellRow'):
723
 
            self.speakCellName(cell.name)
724
 
 
725
 
        try:
726
 
            text = newFocus.queryText()
727
 
        except:
728
 
            offset = -1
729
 
        else:
730
 
            offset = text.caretOffset
731
 
 
732
 
        self.lastCell = [cell, offset]
733
 
        index = self.utilities.cellIndex(cell)
734
 
        column = newTable.getColumnAtIndex(index)
735
 
        self.pointOfReference['lastColumn'] = column
736
 
        row = newTable.getRowAtIndex(index)
737
 
        self.pointOfReference['lastRow'] = row
738
 
 
739
 
        return True
740
 
 
741
403
    def panBrailleLeft(self, inputEvent=None, panAmount=0):
742
404
        """In document content, we want to use the panning keys to browse the
743
405
        entire document.
745
407
 
746
408
        if self.flatReviewContext \
747
409
           or not self.isBrailleBeginningShowing() \
748
 
           or self.isSpreadSheetCell(orca_state.locusOfFocus):
 
410
           or self.utilities.isSpreadSheetCell(orca_state.locusOfFocus) \
 
411
           or not self.utilities.isTextArea(orca_state.locusOfFocus):
749
412
            return default.Script.panBrailleLeft(self, inputEvent, panAmount)
750
413
 
 
414
        text = orca_state.locusOfFocus.queryText()
 
415
        string, startOffset, endOffset = text.getTextAtOffset(
 
416
            text.caretOffset, pyatspi.TEXT_BOUNDARY_LINE_START)
 
417
        if 0 < startOffset:
 
418
            text.setCaretOffset(startOffset-1)
 
419
            return True
 
420
 
751
421
        obj = self.utilities.findPreviousObject(orca_state.locusOfFocus)
752
 
        orca.setLocusOfFocus(None, obj, notifyScript=False)
753
 
        self.updateBraille(obj)
754
 
 
755
 
        # Hack: When panning to the left in a document, we want to start at
756
 
        # the right/bottom of each new object. For now, we'll pan there.
757
 
        # When time permits, we'll give our braille code some smarts.
758
 
        while self.panBrailleInDirection(panToLeft=False):
 
422
        try:
 
423
            text = obj.queryText()
 
424
        except:
759
425
            pass
760
 
        self.refreshBraille(False)
 
426
        else:
 
427
            orca.setLocusOfFocus(None, obj, notifyScript=False)
 
428
            text.setCaretOffset(text.characterCount)
 
429
            return True
761
430
 
762
 
        return True
 
431
        return default.Script.panBrailleLeft(self, inputEvent, panAmount)
763
432
 
764
433
    def panBrailleRight(self, inputEvent=None, panAmount=0):
765
434
        """In document content, we want to use the panning keys to browse the
768
437
 
769
438
        if self.flatReviewContext \
770
439
           or not self.isBrailleEndShowing() \
771
 
           or self.isSpreadSheetCell(orca_state.locusOfFocus):
 
440
           or self.utilities.isSpreadSheetCell(orca_state.locusOfFocus) \
 
441
           or not self.utilities.isTextArea(orca_state.locusOfFocus):
772
442
            return default.Script.panBrailleRight(self, inputEvent, panAmount)
773
443
 
 
444
        text = orca_state.locusOfFocus.queryText()
 
445
        string, startOffset, endOffset = text.getTextAtOffset(
 
446
            text.caretOffset, pyatspi.TEXT_BOUNDARY_LINE_START)
 
447
        if endOffset < text.characterCount:
 
448
            text.setCaretOffset(endOffset)
 
449
            return True
 
450
 
774
451
        obj = self.utilities.findNextObject(orca_state.locusOfFocus)
775
 
        orca.setLocusOfFocus(None, obj, notifyScript=False)
776
 
        self.updateBraille(obj)
777
 
 
778
 
        # Hack: When panning to the right in a document, we want to start at
779
 
        # the left/top of each new object. For now, we'll pan there. When time
780
 
        # permits, we'll give our braille code some smarts.
781
 
        while self.panBrailleInDirection(panToLeft=True):
 
452
        try:
 
453
            text = obj.queryText()
 
454
        except:
782
455
            pass
783
 
        self.refreshBraille(False)
 
456
        else:
 
457
            orca.setLocusOfFocus(None, obj, notifyScript=False)
 
458
            text.setCaretOffset(0)
 
459
            return True
784
460
 
785
 
        return True
 
461
        return default.Script.panBrailleRight(self, inputEvent, panAmount)
786
462
 
787
463
    def presentInputLine(self, inputEvent):
788
464
        """Presents the contents of the spread sheet input line (assuming we
796
472
        - inputEvent: if not None, the input event that caused this action.
797
473
        """
798
474
 
799
 
        debug.println(self.debugLevel, "StarOffice.speakInputLine.")
800
 
 
801
 
        # Check to see if the current focus is a table cell.
802
 
        #
803
 
        if self.isSpreadSheetCell(orca_state.locusOfFocus):
804
 
            try:
805
 
                if self.inputLineForCell and self.inputLineForCell.queryText():
806
 
                    inputLine = \
807
 
                        self.utilities.substring(self.inputLineForCell, 0, -1)
808
 
                    if not inputLine:
809
 
                        inputLine = messages.EMPTY
810
 
                    debug.println(self.debugLevel,
811
 
                        "StarOffice.speakInputLine: contents: %s" % inputLine)
812
 
                    self.displayBrailleMessage(inputLine, \
813
 
                      flashTime=_settingsManager.getSetting('brailleFlashTime'))
814
 
                    speech.speak(inputLine)
815
 
            except NotImplementedError:
816
 
                pass
817
 
 
818
 
    def getTableRow(self, cell):
819
 
        """Get the row number in the table that this table cell is on.
820
 
 
821
 
        Arguments:
822
 
        - cell: the table cell to get the row number for.
823
 
 
824
 
        Return the row number that this table cell is on, or None if
825
 
        this isn't a table cell.
826
 
        """
827
 
 
828
 
        row = None
829
 
        cell = self.adjustForWriterTable(cell)
830
 
        if cell.getRole() == pyatspi.ROLE_TABLE_CELL:
831
 
            parent = cell.parent
832
 
            try:
833
 
                parentTable = parent.queryTable()
834
 
            except NotImplementedError:
835
 
                parentTable = None
836
 
 
837
 
            if parent and parentTable:
838
 
                index = self.utilities.cellIndex(cell)
839
 
                row = parentTable.getRowAtIndex(index)
840
 
 
841
 
        return row
842
 
 
843
 
    def getTableColumn(self, cell):
844
 
        """Get the column number in the table that this table cell is on.
845
 
 
846
 
        Arguments:
847
 
        - cell: the table cell to get the column number for.
848
 
 
849
 
        Return the column number that this table cell is on, or None if
850
 
        this isn't a table cell.
851
 
        """
852
 
 
853
 
        column = None
854
 
        cell = self.adjustForWriterTable(cell)
855
 
        if cell.getRole() == pyatspi.ROLE_TABLE_CELL:
856
 
            parent = cell.parent
857
 
            try:
858
 
                parentTable = parent.queryTable()
859
 
            except NotImplementedError:
860
 
                parentTable = None
861
 
 
862
 
            if parent and parentTable:
863
 
                index = self.utilities.cellIndex(cell)
864
 
                column = parentTable.getColumnAtIndex(index)
865
 
 
866
 
        return column
 
475
        if not self.utilities.isSpreadSheetCell(orca_state.locusOfFocus):
 
476
            return
 
477
 
 
478
        inputLine = self.utilities.locateInputLine(orca_state.locusOfFocus)
 
479
        if not inputLine:
 
480
            return
 
481
 
 
482
        text = self.utilities.displayedText(inputLine)
 
483
        if not text:
 
484
            text = messages.EMPTY
 
485
 
 
486
        self.presentMessage(text)
867
487
 
868
488
    def setDynamicColumnHeaders(self, inputEvent):
869
489
        """Set the row for the dynamic header columns to use when speaking
877
497
        - inputEvent: if not None, the input event that caused this action.
878
498
        """
879
499
 
880
 
        debug.println(self.debugLevel, "StarOffice.setDynamicColumnHeaders.")
 
500
        cell = orca_state.locusOfFocus
 
501
        if cell and cell.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
 
502
            cell = cell.parent
881
503
 
882
 
        table = self.getTable(orca_state.locusOfFocus)
 
504
        row, column, table = self.utilities.getRowColumnAndTable(cell)
883
505
        if table:
884
 
            row = self.getTableRow(orca_state.locusOfFocus)
885
506
            self.dynamicColumnHeaders[hash(table)] = row
886
 
            line = messages.DYNAMIC_COLUMN_HEADER_SET % (row+1)
887
 
            self.presentMessage(line)
 
507
            self.presentMessage(messages.DYNAMIC_COLUMN_HEADER_SET % (row+1))
888
508
 
889
509
        return True
890
510
 
895
515
        - inputEvent: if not None, the input event that caused this action.
896
516
        """
897
517
 
898
 
        debug.println(self.debugLevel, "StarOffice.clearDynamicColumnHeaders.")
 
518
        cell = orca_state.locusOfFocus
 
519
        if cell and cell.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
 
520
            cell = cell.parent
899
521
 
900
 
        table = self.getTable(orca_state.locusOfFocus)
901
 
        if table:
902
 
            row = self.getTableRow(orca_state.locusOfFocus)
903
 
            try:
904
 
                del self.dynamicColumnHeaders[hash(table)]
905
 
                line = messages.DYNAMIC_COLUMN_HEADER_CLEARED
906
 
                speech.stop()
907
 
                self.presentMessage(line)
908
 
            except:
909
 
                pass
 
522
        row, column, table = self.utilities.getRowColumnAndTable(cell)
 
523
        try:
 
524
            del self.dynamicColumnHeaders[hash(table)]
 
525
            speech.stop()
 
526
            self.presentMessage(messages.DYNAMIC_COLUMN_HEADER_CLEARED)
 
527
        except:
 
528
            pass
910
529
 
911
530
        return True
912
531
 
945
564
        - inputEvent: if not None, the input event that caused this action.
946
565
        """
947
566
 
948
 
        debug.println(self.debugLevel, "StarOffice.setDynamicRowHeaders.")
 
567
        cell = orca_state.locusOfFocus
 
568
        if cell and cell.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
 
569
            cell = cell.parent
949
570
 
950
 
        table = self.getTable(orca_state.locusOfFocus)
 
571
        row, column, table = self.utilities.getRowColumnAndTable(cell)
951
572
        if table:
952
 
            column = self.getTableColumn(orca_state.locusOfFocus)
953
573
            self.dynamicRowHeaders[hash(table)] = column
954
 
            line = messages.DYNAMIC_ROW_HEADER_SET % self.columnConvert(column+1)
955
 
            self.presentMessage(line)
 
574
            self.presentMessage(
 
575
                messages.DYNAMIC_ROW_HEADER_SET % self.columnConvert(column+1))
956
576
 
957
577
        return True
958
578
 
963
583
        - inputEvent: if not None, the input event that caused this action.
964
584
        """
965
585
 
966
 
        debug.println(self.debugLevel, "StarOffice.clearDynamicRowHeaders.")
 
586
        cell = orca_state.locusOfFocus
 
587
        if cell and cell.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
 
588
            cell = cell.parent
967
589
 
968
 
        table = self.getTable(orca_state.locusOfFocus)
969
 
        if table:
970
 
            column = self.getTableColumn(orca_state.locusOfFocus)
971
 
            try:
972
 
                del self.dynamicRowHeaders[hash(table)]
973
 
                line = messages.DYNAMIC_ROW_HEADER_CLEARED
974
 
                speech.stop()
975
 
                self.presentMessage(line)
976
 
            except:
977
 
                pass
 
590
        row, column, table = self.utilities.getRowColumnAndTable(cell)
 
591
        try:
 
592
            del self.dynamicRowHeaders[hash(table)]
 
593
            speech.stop()
 
594
            self.presentMessage(messages.DYNAMIC_ROW_HEADER_CLEARED)
 
595
        except:
 
596
            pass
978
597
 
979
598
        return True
980
599
 
1053
672
        # from the last time this routine was called. If they are the same
1054
673
        # then we ignore it.
1055
674
        #
1056
 
        debug.println(self.debugLevel, \
 
675
        debug.println(debug.LEVEL_INFO,
1057
676
            "StarOffice.readMisspeltWord: type=%s  word=%s(%d,%d)  len=%d" % \
1058
677
            (event.type, badWord, startOff, endOff, textLength))
1059
678
 
1079
698
 
1080
699
        return True
1081
700
 
1082
 
    def endOfLink(self, obj, word, startOffset, endOffset):
1083
 
        """Return an indication of whether the given word contains the
1084
 
           end of a hypertext link.
1085
 
 
1086
 
        Arguments:
1087
 
        - obj: an Accessible object that implements the AccessibleText
1088
 
               interface
1089
 
        - word: the word to check
1090
 
        - startOffset: the start offset for this word
1091
 
        - endOffset: the end offset for this word
1092
 
 
1093
 
        Returns True if this word contains the end of a hypertext link.
1094
 
        """
1095
 
 
1096
 
        nLinks = obj.queryHypertext().getNLinks()
1097
 
        links = []
1098
 
        for i in range(0, nLinks):
1099
 
            links.append(obj.queryHypertext().getLink(i))
1100
 
 
1101
 
        for link in links:
1102
 
            if link.endIndex > startOffset and \
1103
 
               link.endIndex <= endOffset:
1104
 
                return True
1105
 
 
1106
 
        return False
1107
 
 
1108
 
    def sayWriterWord(self, obj, word, startOffset, endOffset):
1109
 
        """Speaks the given word in the appropriate voice. If this word is
1110
 
        a hypertext link and it is also at the end offset for one of the
1111
 
        links, then the word "link" is also spoken.
1112
 
 
1113
 
        Arguments:
1114
 
        - obj: an Accessible object that implements the AccessibleText
1115
 
               interface
1116
 
        - word: the word to speak
1117
 
        - startOffset: the start offset for this word
1118
 
        - endOffset: the end offset for this word
1119
 
        """
1120
 
 
1121
 
        voices = _settingsManager.getSetting('voices')
1122
 
 
1123
 
        for i in range(startOffset, endOffset):
1124
 
            if self.utilities.linkIndex(obj, i) >= 0:
1125
 
                voice = voices[settings.HYPERLINK_VOICE]
1126
 
                break
1127
 
            elif word.isupper():
1128
 
                voice = voices[settings.UPPERCASE_VOICE]
1129
 
            else:
1130
 
                voice = voices[settings.DEFAULT_VOICE]
1131
 
 
1132
 
        speech.speak(word, voice)
1133
 
        if self.endOfLink(obj, word, startOffset, endOffset):
1134
 
            speech.speak(messages.LINK)
1135
 
 
1136
 
    def speakSetupLabel(self, label):
1137
 
        """Speak this Setup dialog label.
1138
 
 
1139
 
        Arguments:
1140
 
        - label: the Setup dialog Label.
1141
 
        """
1142
 
 
1143
 
        text = self.utilities.displayedText(label)
1144
 
        if text:
1145
 
            speech.speak(text)
1146
 
 
1147
 
    def handleSetupPanel(self, panel):
1148
 
        """Find all the labels in this Setup panel and speak them.
1149
 
 
1150
 
        Arguments:
1151
 
        - panel: the Setup panel.
1152
 
        """
1153
 
 
1154
 
        allLabels = self.utilities.descendantsWithRole(
1155
 
            panel, pyatspi.ROLE_LABEL)
1156
 
        for label in allLabels:
1157
 
            self.speakSetupLabel(label)
1158
 
 
1159
 
    def _speakWriterText(self, event, textToSpeak):
1160
 
        """Called to speak the current line or paragraph of Writer text.
1161
 
 
1162
 
        Arguments:
1163
 
        - event: the Event
1164
 
        - textToSpeak: the text to speak
1165
 
        """
1166
 
 
1167
 
        if not textToSpeak and event and self.speakBlankLine(event.source):
1168
 
            speech.speak(messages.BLANK, None, False)
1169
 
 
1170
 
        # Check to see if there are any hypertext links in this paragraph.
1171
 
        # If no, then just speak the whole line. Otherwise, split the text
1172
 
        # to speak into words and call sayWriterWord() to speak that token
1173
 
        # in the appropriate voice.
1174
 
        #
1175
 
        try:
1176
 
            hypertext = event.source.queryHypertext()
1177
 
        except NotImplementedError:
1178
 
            hypertext = None
1179
 
 
1180
 
        if not hypertext or (hypertext.getNLinks() == 0):
1181
 
            result = self.speechGenerator.generateTextIndentation(
1182
 
              event.source, line=textToSpeak)
1183
 
            if result:
1184
 
                speech.speak(result[0])
1185
 
 
1186
 
            speech.speak(textToSpeak, None, False)
1187
 
        else:
1188
 
            started = False
1189
 
            startOffset = 0
1190
 
            for i in range(0, len(textToSpeak)):
1191
 
                if textToSpeak[i] == ' ':
1192
 
                    if started:
1193
 
                        endOffset = i
1194
 
                        self.sayWriterWord(event.source,
1195
 
                            textToSpeak[startOffset:endOffset+1],
1196
 
                            startOffset, endOffset)
1197
 
                        startOffset = i
1198
 
                        started = False
1199
 
                else:
1200
 
                    if not started:
1201
 
                        startOffset = i
1202
 
                        started = True
1203
 
 
1204
 
            if started:
1205
 
                endOffset = len(textToSpeak)
1206
 
                self.sayWriterWord(event.source,
1207
 
                    textToSpeak[startOffset:endOffset], startOffset, endOffset)
1208
 
 
1209
701
    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
1210
702
        """Called when the visual object with focus changes.
1211
703
 
1215
707
        - newLocusOfFocus: Accessible that is the new locus of focus
1216
708
        """
1217
709
 
1218
 
        brailleGen = self.brailleGenerator
1219
 
        details = debug.getAccessibleDetails(self.debugLevel, event.source)
1220
 
        debug.printObjectEvent(self.debugLevel, event, details)
1221
 
 
1222
710
        # Check to see if this is this is for the find command. See
1223
711
        # comment #18 of bug #354463.
1224
712
        #
1228
716
            self.find()
1229
717
            return
1230
718
 
1231
 
        # We always automatically go back to focus tracking mode when
1232
 
        # the focus changes.
1233
 
        #
1234
719
        if self.flatReviewContext:
1235
720
            self.toggleFlatReviewMode()
1236
721
 
1237
 
        # If we are inside a paragraph inside a table cell (in Writer),
1238
 
        # then speak/braille that parent table cell (see bug #382415).
1239
 
        # Also announce that a table has been entered or left.
1240
 
        #
1241
 
        if event.source.getRole() == pyatspi.ROLE_PARAGRAPH:
1242
 
            if self.presentTableInfo(oldLocusOfFocus, newLocusOfFocus):
1243
 
                return
1244
 
 
1245
 
            rolesList = [pyatspi.ROLE_PARAGRAPH,
1246
 
                         [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
1247
 
                         pyatspi.ROLE_SCROLL_PANE,
1248
 
                         pyatspi.ROLE_PANEL,
1249
 
                         pyatspi.ROLE_ROOT_PANE,
1250
 
                         pyatspi.ROLE_FRAME]
1251
 
            if self.utilities.hasMatchingHierarchy(event.source, rolesList):
1252
 
                debug.println(self.debugLevel,
1253
 
                   "StarOffice.locusOfFocusChanged - Writer: text paragraph.")
1254
 
 
1255
 
                result = self.getTextLineAtCaret(event.source)
1256
 
                textToSpeak = result[0]
1257
 
                self._speakWriterText(event, textToSpeak)
1258
 
                self.displayBrailleForObject(event.source)
1259
 
                return
1260
 
 
1261
 
        # Check to see if we are editing a spread sheet cell. If so, just
1262
 
        # return to avoid uttering something like "Paragraph 0 paragraph".
1263
 
        #
1264
 
        rolesList = [pyatspi.ROLE_PARAGRAPH,
1265
 
                     [pyatspi.ROLE_PANEL, pyatspi.ROLE_EXTENDED],
1266
 
                     [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
1267
 
                     pyatspi.ROLE_SCROLL_PANE,
1268
 
                     pyatspi.ROLE_PANEL,
1269
 
                     pyatspi.ROLE_ROOT_PANE,
1270
 
                     pyatspi.ROLE_FRAME,
1271
 
                     pyatspi.ROLE_APPLICATION]
1272
 
        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
1273
 
            debug.println(self.debugLevel, "StarOffice.locusOfFocusChanged - " \
1274
 
                          + "Calc: cell editor.")
1275
 
            return
1276
 
 
1277
 
        # Check to see if this is a Calc: spread sheet cell. If it is then
1278
 
        # we don't want to speak "not selected" after giving the cell
1279
 
        # location and contents (which is what the default locusOfFocusChanged
1280
 
        # method would now do).
1281
 
        #
1282
 
        if self.isSpreadSheetCell(event.source, True):
1283
 
            if newLocusOfFocus:
1284
 
                self.updateBraille(newLocusOfFocus)
1285
 
                utterances = \
1286
 
                    self.speechGenerator.generateSpeech(newLocusOfFocus)
1287
 
                speech.speak(utterances)
1288
 
 
1289
 
                # Save the current row and column information in the table
1290
 
                # cell's table, so that we can use it the next time.
1291
 
                #
1292
 
                try:
1293
 
                    table = newLocusOfFocus.parent.queryTable()
1294
 
                except:
1295
 
                    pass
1296
 
                else:
1297
 
                    index = self.utilities.cellIndex(newLocusOfFocus)
1298
 
                    column = table.getColumnAtIndex(index)
1299
 
                    self.pointOfReference['lastColumn'] = column
1300
 
                    row = table.getRowAtIndex(index)
1301
 
                    self.pointOfReference['lastRow'] = row
1302
 
                return
1303
 
 
 
722
        # TODO - JD: Sad hack that wouldn't be needed if LO were fixed.
1304
723
        # If we are in the slide presentation scroll pane, also announce
1305
724
        # the current page tab. See bug #538056 for more details.
1306
725
        #
1310
729
                     pyatspi.ROLE_ROOT_PANE,
1311
730
                     pyatspi.ROLE_FRAME,
1312
731
                     pyatspi.ROLE_APPLICATION]
1313
 
 
1314
732
        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
1315
 
            debug.println(self.debugLevel, "soffice.locusOfFocusChanged - " \
1316
 
                          + "Impress: scroll pane.")
1317
 
 
1318
733
            for child in event.source.parent:
1319
734
                if child.getRole() == pyatspi.ROLE_PAGE_TAB_LIST:
1320
735
                    for tab in child:
1323
738
                            utterances = \
1324
739
                                self.speechGenerator.generateSpeech(tab)
1325
740
                            speech.speak(utterances)
1326
 
            # Fall-thru to process the event with the default handler.
1327
 
 
1328
 
        # If we are focused on a place holder element in the slide
1329
 
        # presentation scroll pane, first present the object, then
1330
 
        # try to present each of its children. See bug #538064 for
1331
 
        # more details.
1332
 
        #
1333
 
        rolesList = [[pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_LIST_ITEM],
1334
 
                     [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
1335
 
                     pyatspi.ROLE_SCROLL_PANE,
1336
 
                     pyatspi.ROLE_PANEL,
1337
 
                     pyatspi.ROLE_PANEL,
1338
 
                     pyatspi.ROLE_ROOT_PANE,
1339
 
                     pyatspi.ROLE_FRAME,
1340
 
                     pyatspi.ROLE_APPLICATION]
1341
 
        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
1342
 
            default.Script.locusOfFocusChanged(self, event,
1343
 
                                    oldLocusOfFocus, newLocusOfFocus)
1344
 
            for child in event.source:
1345
 
                speech.speak(self.utilities.substring(child, 0, -1),
1346
 
                             None, False)
1347
 
            return
1348
 
 
1349
 
        # Combo boxes in OOo typically have two children: a text object
1350
 
        # and a list. The combo box will often intially claim to have
1351
 
        # focus, but not always. The list inside of it, however, will
1352
 
        # claim focus quite regularly. In addition, the list will do
1353
 
        # this even if the text object is editable and functionally has
1354
 
        # focus, such as the File Name combo box in the Save As dialog.
1355
 
        # We need to minimize chattiness and maximize useful information.
1356
 
        #
1357
 
        if newLocusOfFocus and oldLocusOfFocus \
1358
 
           and newLocusOfFocus.getRole() == pyatspi.ROLE_LIST \
1359
 
           and newLocusOfFocus.parent.getRole() == pyatspi.ROLE_COMBO_BOX \
1360
 
           and not self.utilities.isSameObject(newLocusOfFocus.parent,
1361
 
                                     oldLocusOfFocus.parent):
1362
 
 
1363
 
            # If the combo box contents cannot be edited, just present the
1364
 
            # combo box. Otherwise, present the text object. The combo
1365
 
            # box will be included as part of the speech context.
1366
 
            #
1367
 
            state = newLocusOfFocus.parent[0].getState()
1368
 
            if not state.contains(pyatspi.STATE_EDITABLE):
1369
 
                newLocusOfFocus = newLocusOfFocus.parent
1370
 
            else:
1371
 
                newLocusOfFocus = newLocusOfFocus.parent[0]
 
741
 
 
742
        # TODO - JD: This is a hack that needs to be done better. For now it
 
743
        # fixes the broken echo previous word on Return.
 
744
        elif newLocusOfFocus and oldLocusOfFocus \
 
745
           and newLocusOfFocus.getRole() == pyatspi.ROLE_PARAGRAPH \
 
746
           and oldLocusOfFocus.getRole() == pyatspi.ROLE_PARAGRAPH \
 
747
           and newLocusOfFocus != oldLocusOfFocus:
 
748
            lastKey, mods = self.utilities.lastKeyAndModifiers()
 
749
            if lastKey == "Return" and _settingsManager.getSetting('enableEchoByWord'):
 
750
                self.echoPreviousWord(oldLocusOfFocus)
 
751
                return
 
752
 
 
753
            # TODO - JD: And this hack is another one that needs to be done better.
 
754
            # But this will get us to speak the entire paragraph when navigation by
 
755
            # paragraph has occurred.
 
756
            event_string, mods = self.utilities.lastKeyAndModifiers()
 
757
            isControlKey = mods & settings.CTRL_MODIFIER_MASK
 
758
            isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
 
759
            if event_string in ["Up", "Down"] and isControlKey and not isShiftKey:
 
760
                if self.utilities.displayedText(newLocusOfFocus):
 
761
                    speech.speak(self.utilities.displayedText(newLocusOfFocus))
 
762
                    self.updateBraille(newLocusOfFocus)
 
763
                    try:
 
764
                        text = newLocusOfFocus.queryText()
 
765
                    except:
 
766
                        pass
 
767
                    else:
 
768
                        self._saveLastCursorPosition(newLocusOfFocus, text.caretOffset)
 
769
                    return
1372
770
 
1373
771
        # Pass the event onto the parent class to be handled in the default way.
1374
 
 
1375
772
        default.Script.locusOfFocusChanged(self, event,
1376
773
                                           oldLocusOfFocus, newLocusOfFocus)
1377
774
 
 
775
        if self.utilities.isDocumentCell(newLocusOfFocus):
 
776
            row, column, table = \
 
777
                self.utilities.getRowColumnAndTable(newLocusOfFocus.parent)
 
778
            self.pointOfReference['lastRow'] = row
 
779
            self.pointOfReference['lastColumn'] = column
 
780
 
1378
781
    def onWindowActivated(self, event):
1379
782
        """Called whenever a property on an object changes.
1380
783
 
1382
785
        - event: the Event
1383
786
        """
1384
787
 
1385
 
        details = debug.getAccessibleDetails(self.debugLevel, event.source)
1386
 
        debug.printObjectEvent(self.debugLevel, event, details)
1387
 
 
1388
 
        # Clear our stored misspelled word history.
1389
788
        self.lastTextLength = -1
1390
789
        self.lastBadWord = ''
1391
790
        self.lastStartOff = -1
1409
808
        - event: the Event
1410
809
        """
1411
810
 
1412
 
        details = debug.getAccessibleDetails(self.debugLevel, event.source)
1413
 
        debug.printObjectEvent(self.debugLevel, event, details)
1414
 
 
1415
811
        # Check to see if if we've had a property-change event for the
1416
812
        # accessible name for the option pane in the spell check dialog.
1417
813
        # This (hopefully) means that the user has just corrected a
1442
838
 
1443
839
        default.Script.onNameChanged(self, event)
1444
840
 
1445
 
    def onFocus(self, event):
1446
 
        """Called whenever an object gets focus.
1447
 
 
1448
 
        Arguments:
1449
 
        - event: the Event
1450
 
        """
1451
 
 
1452
 
        # If we've used structural navigation commands to get here, the
1453
 
        # presentation will be handled by the StructuralNavigation class.
1454
 
        # The subsequent event will result in redundant presentation.
1455
 
        #
1456
 
        if self.isStructuralNavigationCommand():
1457
 
            return
1458
 
 
1459
 
        # If this is a "focus:" event for the Calc Name combo box, catch
1460
 
        # it here to reduce verbosity (see bug #364407).
1461
 
        #
1462
 
        rolesList = [pyatspi.ROLE_LIST,
1463
 
                     pyatspi.ROLE_COMBO_BOX,
1464
 
                     pyatspi.ROLE_TOOL_BAR,
1465
 
                     pyatspi.ROLE_PANEL,
1466
 
                     pyatspi.ROLE_ROOT_PANE,
1467
 
                     pyatspi.ROLE_FRAME,
1468
 
                     pyatspi.ROLE_APPLICATION]
1469
 
        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
1470
 
            debug.println(self.debugLevel, "StarOffice.onFocus - " \
1471
 
                          + "Calc: Name combo box.")
1472
 
            orca.setLocusOfFocus(event, event.source)
1473
 
            return
1474
 
 
1475
 
        # OOo Writer gets rather enthusiastic with focus: events for lists.
1476
 
        # See bug 546941.
1477
 
        #
1478
 
        if event.source.getRole() == pyatspi.ROLE_LIST \
1479
 
           and orca_state.locusOfFocus \
1480
 
           and self.utilities.isSameObject(
1481
 
                orca_state.locusOfFocus.parent, event.source):
1482
 
            return
1483
 
 
1484
 
        # Auto-inserted bullets and numbers are presented in braille, but not
1485
 
        # spoken. So we'll speak them before sending this event off to the
1486
 
        # default script.
1487
 
        #
1488
 
        if self.utilities.isAutoTextEvent(event):
1489
 
            speech.speak(self.speechGenerator.generateSpeech(event.source))
1490
 
 
1491
 
        default.Script.onFocus(self, event)
 
841
    def onActiveChanged(self, event):
 
842
        """Callback for object:state-changed:active accessibility events."""
 
843
 
 
844
        # Prevent this events from activating the find operation.
 
845
        # See comment #18 of bug #354463.
 
846
        if self.findCommandRun:
 
847
            return
 
848
 
 
849
        default.Script.onActiveChanged(self, event)
1492
850
 
1493
851
    def onActiveDescendantChanged(self, event):
1494
852
        """Called when an object who manages its own descendants detects a
1498
856
        - event: the Event
1499
857
        """
1500
858
 
1501
 
        handleEvent = False
1502
 
        presentEvent = True
1503
 
        if not event.source.getState().contains(pyatspi.STATE_FOCUSED):
1504
 
            # Sometimes the items in the OOo Task Pane give up focus (e.g.
1505
 
            # to a context menu) and never reclaim it. In this case, we
1506
 
            # still get object:active-descendant-changed events, but the
1507
 
            # event.source lacks STATE_FOCUSED. This causes the default
1508
 
            # script to ignore the event. See bug #523416. [[[TODO - JD:
1509
 
            # If the OOo guys fix this on their end, this hack should be
1510
 
            # removed. The OOo issue can be found here:
1511
 
            # http://www.openoffice.org/issues/show_bug.cgi?id=93083]]]
1512
 
            #
1513
 
            rolesList = [pyatspi.ROLE_LIST,
1514
 
                         pyatspi.ROLE_PANEL,
1515
 
                         pyatspi.ROLE_PANEL,
1516
 
                         pyatspi.ROLE_LIST_ITEM]
1517
 
            if self.utilities.hasMatchingHierarchy(event.source, rolesList) \
1518
 
               and event.any_data:
1519
 
                handleEvent = True
1520
 
 
1521
 
            # The style list in the Formatting toolbar also lacks state
1522
 
            # focused.
1523
 
            #
1524
 
            elif event.any_data \
1525
 
                 and self.utilities.ancestorWithRole(event.source,
1526
 
                                                     [pyatspi.ROLE_TOOL_BAR],
1527
 
                                                     [pyatspi.ROLE_FRAME]) \
1528
 
                 and self.utilities.ancestorWithRole(orca_state.locusOfFocus,
1529
 
                                                     [pyatspi.ROLE_TOOL_BAR],
1530
 
                                                     [pyatspi.ROLE_FRAME]):
1531
 
                handleEvent = True
1532
 
 
1533
 
        elif self.utilities.isSameObject(
1534
 
                orca_state.locusOfFocus, event.source.parent) \
1535
 
             and event.source.getRole() == pyatspi.ROLE_LIST \
1536
 
             and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_COMBO_BOX:
1537
 
            # Combo boxes which have been explicitly given focus by the user
1538
 
            # (as opposed to those which have been automatically given focus
1539
 
            # in a dialog or alert) issue an object:state-changed:focused
1540
 
            # event, then an object:active-descendant-changed event for the
1541
 
            # list inside the combo box, and finally a focus: event for the
1542
 
            # list itself. This leads to unnecessary chattiness. As these
1543
 
            # objects look and act like combo boxes, we'll let the first of
1544
 
            # the events cause the object to be presented. Quietly setting
1545
 
            # the locusOfFocus to the activeDescendant here will prevent
1546
 
            # this event's chattiness. The final focus: event for the list
1547
 
            # is already being handled by onFocus as part of bug 546941.
1548
 
            #
1549
 
            handleEvent = True
1550
 
            presentEvent = False
1551
 
 
1552
 
        if orca_state.locusOfFocus \
1553
 
           and self.utilities.isSameObject(
1554
 
            orca_state.locusOfFocus, event.any_data):
1555
 
            # We're already on the new item. If we (or the default script)
1556
 
            # presents it, the speech.stop() will cause us to interrupt the
1557
 
            # presentation we're probably about to make due to an earlier
1558
 
            # event.
1559
 
            #
1560
 
            handleEvent = True
1561
 
            presentEvent = False
1562
 
 
1563
 
        if handleEvent:
1564
 
            if presentEvent:
1565
 
                speech.stop()
1566
 
            orca.setLocusOfFocus(
1567
 
                event, event.any_data, notifyScript=presentEvent)
1568
 
 
1569
 
            # We'll tuck away the activeDescendant information for future
1570
 
            # reference since the AT-SPI gives us little help in finding
1571
 
            # this.
1572
 
            #
1573
 
            self.pointOfReference['activeDescendantInfo'] = \
1574
 
                [orca_state.locusOfFocus.parent,
1575
 
                 orca_state.locusOfFocus.getIndexInParent()]
 
859
        if self.utilities.isSameObject(event.any_data, orca_state.locusOfFocus):
1576
860
            return
1577
861
 
1578
862
        default.Script.onActiveDescendantChanged(self, event)
1584
868
        - event: the Event
1585
869
        """
1586
870
 
1587
 
        if not event.any_data or not orca_state.locusOfFocus:
1588
 
            return
1589
 
 
1590
 
        if not event.type.startswith('object:children-changed:add'):
1591
 
            return
1592
 
 
1593
871
        try:
1594
 
            role = event.any_data.getRole()
 
872
            anyDataRole = event.any_data.getRole()
1595
873
        except:
1596
 
            role = None
1597
 
        if role == pyatspi.ROLE_TABLE:
1598
 
            if self.isSpreadSheetCell(event.any_data, True):
 
874
            return
 
875
 
 
876
        if anyDataRole == pyatspi.ROLE_TABLE:
 
877
            if self.utilities.isSpreadSheetCell(event.any_data, True):
1599
878
                orca.setLocusOfFocus(event, event.any_data)
1600
879
            return
1601
880
 
1602
 
        if role == pyatspi.ROLE_TABLE_CELL:
 
881
        if anyDataRole == pyatspi.ROLE_TABLE_CELL:
1603
882
            activeRow = self.pointOfReference.get('lastRow', -1)
1604
883
            activeCol = self.pointOfReference.get('lastColumn', -1)
1605
884
            if activeRow < 0 or activeCol < 0:
1606
885
                return
1607
886
 
 
887
            eventRow, eventCol, table = \
 
888
                self.utilities.getRowColumnAndTable(event.any_data)
1608
889
            try:
1609
 
                itable = event.source.queryTable()
 
890
                itable = table.queryTable()
1610
891
            except NotImplementedError:
1611
892
                return
1612
893
 
1613
 
            index = self.utilities.cellIndex(event.any_data)
1614
 
            eventRow = itable.getRowAtIndex(index)
1615
 
            eventCol = itable.getColumnAtIndex(index)
1616
 
 
1617
894
            if eventRow == itable.nRows - 1 and eventCol == itable.nColumns - 1:
1618
895
                fullMessage = briefMessage = ""
1619
896
                voice = self.voices.get(settings.SYSTEM_VOICE)
1625
902
                    briefMessage = messages.TABLE_ROW_INSERTED
1626
903
                if fullMessage:
1627
904
                    self.presentMessage(fullMessage, briefMessage, voice)
1628
 
 
1629
 
    def onStateChanged(self, event):
1630
 
        """Called whenever an object's state changes.
1631
 
 
1632
 
        Arguments:
1633
 
        - event: the Event
1634
 
        """
1635
 
 
1636
 
        if event.source.getRole() == pyatspi.ROLE_EXTENDED:
1637
 
            if event.source.getRoleName() == 'text frame':
1638
905
                return
1639
906
 
 
907
        default.Script.onChildrenChanged(self, event)
 
908
 
 
909
    def onFocus(self, event):
 
910
        """Callback for focus: accessibility events."""
 
911
 
 
912
        # NOTE: This event type is deprecated and Orca should no longer use it.
 
913
        # This callback remains just to handle bugs in applications and toolkits
 
914
        # during the remainder of the unstable (3.11) development cycle.
 
915
 
 
916
        role = event.source.getRole()
 
917
 
 
918
        # This seems to be something we inherit from Gtk+
 
919
        if role in [pyatspi.ROLE_TEXT, pyatspi.ROLE_PASSWORD_TEXT]:
 
920
            orca.setLocusOfFocus(event, event.source)
 
921
            return
 
922
 
 
923
        # Ditto.
 
924
        if role == pyatspi.ROLE_PUSH_BUTTON:
 
925
            orca.setLocusOfFocus(event, event.source)
 
926
            return
 
927
 
 
928
        # Ditto.
 
929
        if role == pyatspi.ROLE_COMBO_BOX:
 
930
            orca.setLocusOfFocus(event, event.source)
 
931
            return
 
932
 
 
933
    def onFocusedChanged(self, event):
 
934
        """Callback for object:state-changed:focused accessibility events."""
 
935
 
 
936
        if self.isStructuralNavigationCommand():
 
937
            return
 
938
 
 
939
        if not event.detail1:
 
940
            return
 
941
 
 
942
        if event.source.getRoleName() == 'text frame':
 
943
            return
 
944
 
 
945
        ignoreRoles = [pyatspi.ROLE_FILLER, pyatspi.ROLE_PANEL]
 
946
        if event.source.getRole() in ignoreRoles:
 
947
            return
 
948
 
1640
949
        parent = event.source.parent
1641
 
        if parent and parent.getRole() == pyatspi.ROLE_EXTENDED:
1642
 
            if parent.getRoleName() == 'text frame':
1643
 
                return
1644
 
 
1645
 
        # If this is state change "focused" event and event.source isn't a
1646
 
        # focused object, then just return. See bug #517502 for more details.
1647
 
        #
1648
 
        if event.type.startswith("object:state-changed:focused") \
1649
 
           and (not event.source.getState().contains(pyatspi.STATE_FOCUSED) \
1650
 
                or event.detail1 == 0):
1651
 
            return
1652
 
 
1653
 
        # Prevent  "object:state-changed:active" events from activating
1654
 
        # the find operation. See comment #18 of bug #354463.
1655
 
        #
1656
 
        if event.type.startswith("object:state-changed:active"):
1657
 
            if self.findCommandRun:
1658
 
                return
1659
 
 
 
950
        if parent and parent.getRoleName() == 'text frame':
 
951
            return
 
952
 
 
953
        obj, offset = self.pointOfReference.get("lastCursorPosition", (None, -1))
 
954
        textSelections = self.pointOfReference.get('textSelections', {})
 
955
        start, end = textSelections.get(hash(obj), (0, 0))
 
956
        if start != end:
 
957
            return
 
958
 
 
959
        if self.utilities._flowsFromOrToSelection(event.source):
 
960
            return
 
961
 
 
962
        # We should present this in response to active-descendant-changed events
 
963
        if event.source.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS):
 
964
            return
 
965
 
 
966
        default.Script.onFocusedChanged(self, event)
 
967
 
 
968
    def onCaretMoved(self, event):
 
969
        """Called whenever the caret moves.
 
970
 
 
971
        Arguments:
 
972
        - event: the Event
 
973
        """
 
974
 
 
975
        if self.isStructuralNavigationCommand():
 
976
            return
 
977
 
 
978
        if event.detail1 == -1:
 
979
            return
 
980
 
 
981
        if self.utilities.isCellBeingEdited(event.source):
 
982
            orca.setLocusOfFocus(event, event.source.parent, False)
 
983
 
 
984
        if not orca_state.locusOfFocus:
 
985
            default.Script.onCaretMoved(self, event)
 
986
            return
 
987
 
 
988
        if orca_state.locusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL:
 
989
            default.Script.onCaretMoved(self, event)
 
990
            return
 
991
 
 
992
        # The lists and combo boxes in the Formatting toolbar emit
 
993
        # object:active-descendant-changed events which cause us
 
994
        # to set the locusOfFocus to the list item. If the user then
 
995
        # arrows within the text portion, we will not present it due
 
996
        # to the event not being from the locusOfFocus. A similar
 
997
        # issue is present in the Target entry of the Hyperlink dialog
 
998
        # for OOo 3.2.
 
999
        #
 
1000
        if event.source.getRole() == pyatspi.ROLE_TEXT \
 
1001
           and self.utilities.ancestorWithRole(
 
1002
               event.source,
 
1003
               [pyatspi.ROLE_TOOL_BAR, pyatspi.ROLE_DIALOG],
 
1004
               [pyatspi.ROLE_FRAME]):
 
1005
            orca.setLocusOfFocus(event, event.source, False)
 
1006
 
 
1007
        if self.utilities._flowsFromOrToSelection(event.source):
 
1008
            return
 
1009
 
 
1010
        default.Script.onCaretMoved(self, event)
 
1011
 
 
1012
    def onCheckedChanged(self, event):
 
1013
        """Callback for object:state-changed:checked accessibility events."""
 
1014
 
 
1015
        obj = event.source
 
1016
        role = obj.getRole()
 
1017
        parentRole = obj.parent.getRole()
 
1018
        if not role in [pyatspi.ROLE_TOGGLE_BUTTON, pyatspi.ROLE_PUSH_BUTTON] \
 
1019
           or not parentRole == pyatspi.ROLE_TOOL_BAR:
 
1020
            default.Script.onCheckedChanged(self, event)
 
1021
            return
 
1022
 
1660
1023
        # Announce when the toolbar buttons are toggled if we just toggled
1661
1024
        # them; not if we navigated to some text.
1662
 
        #
1663
 
        if event.type.startswith("object:state-changed:checked") and \
1664
 
           (event.source.getRole() == pyatspi.ROLE_TOGGLE_BUTTON or \
1665
 
            event.source.getRole() == pyatspi.ROLE_PUSH_BUTTON):
1666
 
            weToggledIt = False
1667
 
            if isinstance(orca_state.lastInputEvent, \
1668
 
                          input_event.MouseButtonEvent):
1669
 
                x = orca_state.lastInputEvent.x
1670
 
                y = orca_state.lastInputEvent.y
1671
 
                weToggledIt = event.source.queryComponent().contains(x, y, 0)
1672
 
 
1673
 
            else:
1674
 
                keyString, mods = self.utilities.lastKeyAndModifiers()
1675
 
                navKeys = ["Up", "Down", "Left", "Right", "Page_Up",
1676
 
                           "Page_Down", "Home", "End"]
1677
 
                wasCommand = mods & settings.COMMAND_MODIFIER_MASK
1678
 
                weToggledIt = wasCommand and keyString not in navKeys
1679
 
 
1680
 
            if weToggledIt:
1681
 
                speech.speak(self.speechGenerator.generateSpeech(event.source))
1682
 
 
1683
 
        # When a new paragraph receives focus, we get a caret-moved event and
1684
 
        # two focus events (the first being object:state-changed:focused).
1685
 
        # The caret-moved event will cause us to present the text at the new
1686
 
        # location, so it is safe to set the locusOfFocus silently here.
1687
 
        # However, if we just created a new paragraph by pressing Return at
1688
 
        # the end of the current paragraph, we will only get a caret-moved
1689
 
        # event for the paragraph that just gave up focus (detail1 == -1).
1690
 
        # In this case, we will keep displaying the previous line of text,
1691
 
        # so we'll do an updateBraille() just in case.
1692
 
        #
1693
 
        if event.type.startswith("object:state-changed:focused"):
1694
 
            rolesList = [pyatspi.ROLE_PARAGRAPH,
1695
 
                         [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
1696
 
                         pyatspi.ROLE_SCROLL_PANE,
1697
 
                         pyatspi.ROLE_PANEL,
1698
 
                         pyatspi.ROLE_ROOT_PANE,
1699
 
                         pyatspi.ROLE_FRAME]
1700
 
            if self.utilities.hasMatchingHierarchy(event.source, rolesList):
1701
 
                orca.setLocusOfFocus(event, event.source, notifyScript=False)
1702
 
                if event.source != self.currentParagraph:
1703
 
                    self.updateBraille(event.source)
1704
 
                return
1705
 
 
1706
 
            # If we get "object:state-changed:focused" events for children of
1707
 
            # a combo-box, just set the focus to the combo box. This is needed
1708
 
            # to help reduce the verbosity of focusing on the Calc Name combo
1709
 
            # box (see bug #364407).
1710
 
            #
1711
 
            elif event.source.parent and \
1712
 
                event.source.parent.getRole() == pyatspi.ROLE_COMBO_BOX:
1713
 
                orca.setLocusOfFocus(
1714
 
                    None, event.source.parent, notifyScript=False)
1715
 
                return
1716
 
 
1717
 
        default.Script.onStateChanged(self, event)
1718
 
 
1719
 
    def onSelectionChanged(self, event):
1720
 
        """Called when an object's selection changes.
1721
 
 
1722
 
        Arguments:
1723
 
        - event: the Event
1724
 
        """
1725
 
 
1726
 
        details = debug.getAccessibleDetails(self.debugLevel, event.source)
1727
 
        debug.printObjectEvent(self.debugLevel, event, details)
1728
 
 
1729
 
        # If this "object:selection-changed" is for the spread sheet Name
1730
 
        # Box, then check to see if the current locus of focus is a spread
1731
 
        # sheet cell. If it is, and the contents of the input line are
1732
 
        # different from what is displayed in that cell, then speak "has
1733
 
        # formula" and append it to the braille line.
1734
 
        #
1735
 
        rolesList = [pyatspi.ROLE_LIST,
1736
 
                     pyatspi.ROLE_COMBO_BOX,
1737
 
                     pyatspi.ROLE_PANEL,
1738
 
                     pyatspi.ROLE_TOOL_BAR,
1739
 
                     pyatspi.ROLE_PANEL,
1740
 
                     pyatspi.ROLE_ROOT_PANE,
1741
 
                     pyatspi.ROLE_FRAME,
1742
 
                     pyatspi.ROLE_APPLICATION]
1743
 
        if self.utilities.hasMatchingHierarchy(event.source, rolesList) \
1744
 
           and orca_state.locusOfFocus:
1745
 
            if orca_state.locusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL:
1746
 
                cell = orca_state.locusOfFocus
1747
 
 
1748
 
                # We are getting two "object:selection-changed" events
1749
 
                # for each spread sheet cell move, so in order to prevent
1750
 
                # appending "has formula" twice, we only do it if the last
1751
 
                # cell is different from this one.
1752
 
                #
1753
 
                if cell != self.lastCell[0]:
1754
 
                    self.lastCell[0] = cell
1755
 
 
1756
 
                    try:
1757
 
                        if cell.queryText():
1758
 
                            cellText = self.utilities.substring(cell, 0, -1)
1759
 
                            if cellText and len(cellText):
1760
 
                                try:
1761
 
                                    if self.inputLineForCell and \
1762
 
                                       self.inputLineForCell.queryText():
1763
 
                                        inputLine = self.utilities.substring( \
1764
 
                                                 self.inputLineForCell, 0, -1)
1765
 
                                        if inputLine and (len(inputLine) > 1) \
1766
 
                                            and (inputLine[0] == "="):
1767
 
                                            hf = messages.HAS_FORMULA
1768
 
                                            speech.speak(" %s" % hf,
1769
 
                                                         None, False)
1770
 
                                            self.presentItemsInBraille([hf])
1771
 
                                            #
1772
 
                                            # Fall-thru to process the event
1773
 
                                            # with the default handler.
1774
 
                                except NotImplementedError:
1775
 
                                    pass
1776
 
                    except NotImplementedError:
1777
 
                        pass
1778
 
 
1779
 
        default.Script.onSelectionChanged(self, event)
1780
 
 
1781
 
    def speakCellName(self, name):
1782
 
        """Speaks the given cell name.
1783
 
 
1784
 
        Arguments:
1785
 
        - name: the name of the cell
1786
 
        """
1787
 
 
1788
 
        line = messages.CELL % name
1789
 
        speech.speak(line)
1790
 
 
1791
 
    def onCaretMoved(self, event):
1792
 
        """Called whenever the caret moves.
1793
 
 
1794
 
        Arguments:
1795
 
        - event: the Event
1796
 
        """
1797
 
 
1798
 
        # If we've used structural navigation commands to get here, the
1799
 
        # presentation will be handled by the StructuralNavigation class.
1800
 
        # The subsequent event will result in redundant presentation.
1801
 
        #
1802
 
        if self.isStructuralNavigationCommand():
1803
 
            return
1804
 
 
1805
 
        if self.utilities.isDuplicateEvent(event):
1806
 
            return
1807
 
 
1808
 
        # If we are losing focus and we in:
1809
 
        # 1/ a paragraph in an ooimpress slide presentation
1810
 
        # 2/ a paragraph in an oowriter text document
1811
 
        # and the last thing the user typed was a Return, and echo by word
1812
 
        # is enabled, and the last focused object was not of role "unknown",
1813
 
        # then echo the previous word that the user typed.
1814
 
        # See bug #538053 and bug #538835 for more details.
1815
 
        #
1816
 
        if event.detail1 == -1:
1817
 
            # ooimpress paragraph in a slide presentation.
1818
 
            rolesList = [pyatspi.ROLE_PARAGRAPH,
1819
 
                         [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_LIST_ITEM],
1820
 
                         [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
1821
 
                         pyatspi.ROLE_SCROLL_PANE,
1822
 
                         pyatspi.ROLE_PANEL,
1823
 
                         pyatspi.ROLE_PANEL,
1824
 
                         pyatspi.ROLE_ROOT_PANE,
1825
 
                         pyatspi.ROLE_FRAME,
1826
 
                         pyatspi.ROLE_APPLICATION]
1827
 
 
1828
 
            # oowriter paragraph in a text document.
1829
 
            rolesList1 = [pyatspi.ROLE_PARAGRAPH,
1830
 
                          [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
1831
 
                          pyatspi.ROLE_SCROLL_PANE,
1832
 
                          pyatspi.ROLE_PANEL,
1833
 
                          pyatspi.ROLE_ROOT_PANE,
1834
 
                          pyatspi.ROLE_FRAME,
1835
 
                          pyatspi.ROLE_APPLICATION]
1836
 
            if _settingsManager.getSetting('enableEchoByWord') and \
1837
 
               (self.utilities.hasMatchingHierarchy(event.source, rolesList) or
1838
 
                self.utilities.hasMatchingHierarchy(event.source, rolesList1)):
1839
 
                keyString, mods = self.utilities.lastKeyAndModifiers()
1840
 
                focusRole = orca_state.locusOfFocus.getRole()
1841
 
                if focusRole != pyatspi.ROLE_UNKNOWN and keyString == "Return":
1842
 
                    result = self.utilities.substring(event.source, 0, -1)
1843
 
                    self.echoPreviousWord(event.source, len(result))
1844
 
                    return
1845
 
 
1846
 
        # Otherwise, if the object is losing focus, then just ignore this event.
1847
 
        #
1848
 
        if event.detail1 == -1:
1849
 
            return
1850
 
 
1851
 
        if self.lastCell[0] == event.source.parent:
1852
 
            if self.lastCell[1] == event.detail1:
1853
 
                # We took care of this in a focus event (our position has not
1854
 
                # changed within the cell)
1855
 
                #
1856
 
                return
1857
 
            else:
1858
 
                # We're in the same cell, but at a different position. Update
1859
 
                # our stored location and then let the normal caret-moved
1860
 
                # processing take place.
1861
 
                #
1862
 
                self.lastCell[1] = event.detail1
1863
 
 
1864
 
        event_string, mods = self.utilities.lastKeyAndModifiers()
1865
 
        isControlKey = mods & settings.CTRL_MODIFIER_MASK
1866
 
        isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
1867
 
 
1868
 
        # If the last input event was a keyboard event of Control-Up or
1869
 
        # Control-Down, we want to speak the whole paragraph rather than
1870
 
        # just the current line. In addition, we need to filter out some
1871
 
        # creative uses of the caret-moved event on the part of the OOo
1872
 
        # guys.
1873
 
        #
1874
 
        if event_string in ["Up", "Down"] and isControlKey and not isShiftKey:
1875
 
            # If we moved to the next paragraph, the event.source index should
1876
 
            # be larger than the current paragraph's index. If we moved to the
1877
 
            # previous paragraph it should be smaller. Otherwise, it's bogus.
1878
 
            #
1879
 
            eventIndex = event.source.getIndexInParent()
1880
 
            if self.currentParagraph:
1881
 
                paraIndex = self.currentParagraph.getIndexInParent()
1882
 
            else:
1883
 
                paraIndex = eventIndex
1884
 
 
1885
 
            if (event_string == "Down" and (eventIndex - paraIndex <= 0)) \
1886
 
               or (event_string == "Up" and (eventIndex - paraIndex >= 0)):
1887
 
                return
1888
 
 
1889
 
            result = self.utilities.substring(event.source, 0, -1)
1890
 
            self._speakWriterText(event, result)
1891
 
            self.displayBrailleForObject(event.source)
1892
 
        else:
1893
 
            # The lists and combo boxes in the Formatting toolbar emit
1894
 
            # object:active-descendant-changed events which cause us
1895
 
            # to set the locusOfFocus to the list item. If the user then
1896
 
            # arrows within the text portion, we will not present it due
1897
 
            # to the event not being from the locusOfFocus. A similar
1898
 
            # issue is present in the Target entry of the Hyperlink dialog
1899
 
            # for OOo 3.2.
1900
 
            #
1901
 
            if event.source.getRole() == pyatspi.ROLE_TEXT \
1902
 
               and self.utilities.ancestorWithRole(
1903
 
                    event.source, 
1904
 
                    [pyatspi.ROLE_TOOL_BAR, pyatspi.ROLE_DIALOG],
1905
 
                    [pyatspi.ROLE_FRAME]):
1906
 
                orca.setLocusOfFocus(event, event.source, False)
1907
 
            default.Script.onCaretMoved(self, event)
1908
 
 
1909
 
        # If we're still here, we must be convinced that this paragraph
1910
 
        # coincides with our actual location.
1911
 
        #
1912
 
        self.currentParagraph = event.source
1913
 
 
1914
 
    def speakBlankLine(self, obj):
1915
 
        """Returns True if a blank line should be spoken.
1916
 
        Otherwise, returns False.
1917
 
        """
1918
 
 
1919
 
        # Get the the AccessibleText interface.
1920
 
        try:
1921
 
            text = obj.queryText()
1922
 
        except NotImplementedError:
1923
 
            return False
1924
 
 
1925
 
        # Get the line containing the caret
1926
 
        caretOffset = text.caretOffset
1927
 
        line = text.getTextAtOffset(caretOffset, \
1928
 
            pyatspi.TEXT_BOUNDARY_LINE_START)
1929
 
 
1930
 
        # If this is a blank line, announce it if the user requested
1931
 
        # that blank lines be spoken.
1932
 
        if line[1] == 0 and line[2] == 0:
1933
 
            return _settingsManager.getSetting('speakBlankLines')
1934
 
 
1935
 
    def onTextInserted(self, event):
1936
 
        """Called whenever text is inserted into an object.  Overridden here
1937
 
        to handle the case when the inserted text was pasted via middle mouse
1938
 
        click.
1939
 
 
1940
 
        Arguments:
1941
 
        - event: the Event
1942
 
        """
1943
 
 
1944
 
        # Because event.source is the paragraph where the text was inserted
1945
 
        # and locusOfFocus is the selected text, the default onTextInserted
1946
 
        # will return without speaking the text that was pasted.
1947
 
        #
1948
 
        text = event.any_data
1949
 
        if isinstance(orca_state.lastInputEvent,
1950
 
                        input_event.MouseButtonEvent) and \
1951
 
             orca_state.lastInputEvent.button == "2":
1952
 
            if text.isupper():
1953
 
                speech.speak(text, self.voices[settings.UPPERCASE_VOICE])
1954
 
            else:
1955
 
                speech.speak(text)
1956
 
        else:
1957
 
            default.Script.onTextInserted(self, event)
 
1025
        weToggledIt = False
 
1026
        if isinstance(orca_state.lastInputEvent, input_event.MouseButtonEvent):
 
1027
            x = orca_state.lastInputEvent.x
 
1028
            y = orca_state.lastInputEvent.y
 
1029
            weToggledIt = obj.queryComponent().contains(x, y, 0)
 
1030
        else:
 
1031
            keyString, mods = self.utilities.lastKeyAndModifiers()
 
1032
            navKeys = ["Up", "Down", "Left", "Right", "Page_Up", "Page_Down",
 
1033
                       "Home", "End"]
 
1034
            wasCommand = mods & settings.COMMAND_MODIFIER_MASK
 
1035
            weToggledIt = wasCommand and keyString not in navKeys
 
1036
        if weToggledIt:
 
1037
            speech.speak(self.speechGenerator.generateSpeech(obj))
 
1038
 
 
1039
    def onRowReordered(self, event):
 
1040
        """Callback for object:row-reordered accessibility events."""
 
1041
 
 
1042
        # We're seeing a crazy ton of these emitted bogusly.
 
1043
        pass
 
1044
 
 
1045
    def onTextAttributesChanged(self, event):
 
1046
        """Callback for object:text-attributes-changed accessibility events."""
 
1047
 
 
1048
        # LibreOffice emits this signal nearly every time text is typed,
 
1049
        # even though the text attributes haven't changed:
 
1050
        # https://bugs.freedesktop.org/show_bug.cgi?id=71556
 
1051
 
 
1052
        # LibreOffice fails to emit this signal the main time we are looking
 
1053
        # for it, namely to present that a misspelled word was typed:
 
1054
        # https://bugs.freedesktop.org/show_bug.cgi?id=71558
 
1055
 
 
1056
        # Useless signals are useless.
 
1057
        pass
1958
1058
 
1959
1059
    def getTextLineAtCaret(self, obj, offset=None):
1960
1060
        """Gets the line of text where the caret is. Overridden here to
1993
1093
            textLine[0] = self.utilities.displayedText(obj)
1994
1094
 
1995
1095
        return textLine
1996