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.
452
- obj: the accessible object to check.
454
Returns parent table cell (if in a Writer table ) or the current
458
if obj.getRole() == pyatspi.ROLE_PARAGRAPH and \
459
obj.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
464
def getTable(self, obj):
465
"""Get the table that this table cell is in.
468
- obj: the table cell.
470
Return the table that this table cell is in, or None if this object
474
obj = self.adjustForWriterTable(obj)
475
if obj.getRole() == pyatspi.ROLE_TABLE_CELL and obj.parent:
477
obj.parent.queryTable()
478
except NotImplementedError:
483
def getDynamicColumnHeaderCell(self, obj, column):
484
"""Given a table cell, return the dynamic column header cell
488
- obj: the table cell.
489
- column: the column that this dynamic header is on.
491
Return the dynamic column header cell associated with the given
495
obj = self.adjustForWriterTable(obj)
499
parentTable = parent.queryTable()
500
except NotImplementedError:
503
if parent and parentTable:
504
index = self.utilities.cellIndex(obj)
505
row = parentTable.getRowAtIndex(index)
506
accCell = parentTable.getAccessibleAt(row, column)
510
def getDynamicRowHeaderCell(self, obj, row):
511
"""Given a table cell, return the dynamic row header cell
515
- obj: the table cell.
516
- row: the row that this dynamic header is on.
518
Return the dynamic row header cell associated with the given
522
obj = self.adjustForWriterTable(obj)
526
parentTable = parent.queryTable()
527
except NotImplementedError:
530
if parent and parentTable:
531
index = self.utilities.cellIndex(obj)
532
column = parentTable.getColumnAtIndex(index)
533
accCell = parentTable.getAccessibleAt(row, column)
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.
546
- obj: the spread sheet table cell that has just got focus.
548
Returns the spread sheet input line component.
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]
559
debug.println(debug.LEVEL_SEVERE,
560
"StarOffice: locateInputLine: incorrect paragraph count.")
562
debug.println(debug.LEVEL_SEVERE,
563
"StarOffice: locateInputLine: couldn't find common panel.")
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).
573
- obj: a spread sheet table cell.
575
Returns the start and end table cell indices.
580
parentTable = parent.queryTable()
581
except NotImplementedError:
585
endIndex = parentTable.nColumns
587
if self.isSpreadSheetCell(obj):
588
extents = parent.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
590
leftX = extents.x + 1
592
parent.queryComponent().getAccessibleAtPoint(leftX, y, 0)
594
table = leftCell.parent.queryTable()
595
index = self.utilities.cellIndex(leftCell)
596
startIndex = table.getColumnAtIndex(index)
598
rightX = extents.x + extents.width - 1
600
parent.queryComponent().getAccessibleAtPoint(rightX, y, 0)
602
table = rightCell.parent.queryTable()
603
index = self.utilities.cellIndex(rightCell)
604
endIndex = table.getColumnAtIndex(index)
606
return [startIndex, endIndex]
608
def isSpreadSheetCell(self, obj, startFromTable=False):
609
"""Return an indication of whether the given obj is a spread sheet
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).
617
Returns True if this is a table cell, False otherwise.
621
if not startFromTable:
625
table = obj.queryTable()
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.
632
if cell.getRole() == pyatspi.ROLE_PARAGRAPH:
633
top = self.utilities.topLevelObject(cell)
634
return (top and top.name.endswith(" Calc"))
638
return table.nRows in [65536, 1048576]
640
def presentTableInfo(self, oldFocus, newFocus):
641
"""Presents information relevant to a table that was just entered
642
(primarily) or exited.
645
- oldFocus: the first accessible to check (usually the previous
647
- newFocus: the second accessible to check (usually the current
650
Returns True if table info was presented.
653
oldAncestor = self.utilities.ancestorWithRole(
656
pyatspi.ROLE_UNKNOWN,
657
pyatspi.ROLE_DOCUMENT_FRAME],
658
[pyatspi.ROLE_FRAME])
659
newAncestor = self.utilities.ancestorWithRole(
662
pyatspi.ROLE_UNKNOWN,
663
pyatspi.ROLE_DOCUMENT_FRAME],
664
[pyatspi.ROLE_FRAME])
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.
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
680
oldTable = oldAncestor.queryTable()
685
newTable = newAncestor.queryTable()
689
if oldTable == newTable == None:
690
# We're in a document, but apparently have not entered or left
695
if not self.utilities.isSameObject(oldAncestor, newAncestor):
697
self.presentMessage(messages.TABLE_LEAVING)
700
messages.tableSize(newTable.nRows, newTable.nColumns))
703
self.lastCell = [None, -1]
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
717
return (cell != None)
719
self.updateBraille(cell)
720
speech.speak(self.speechGenerator.generateSpeech(cell))
722
if not _settingsManager.getSetting('readTableCellRow'):
723
self.speakCellName(cell.name)
726
text = newFocus.queryText()
730
offset = text.caretOffset
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
741
403
def panBrailleLeft(self, inputEvent=None, panAmount=0):
742
404
"""In document content, we want to use the panning keys to browse the
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.
1087
- obj: an Accessible object that implements the AccessibleText
1089
- word: the word to check
1090
- startOffset: the start offset for this word
1091
- endOffset: the end offset for this word
1093
Returns True if this word contains the end of a hypertext link.
1096
nLinks = obj.queryHypertext().getNLinks()
1098
for i in range(0, nLinks):
1099
links.append(obj.queryHypertext().getLink(i))
1102
if link.endIndex > startOffset and \
1103
link.endIndex <= endOffset:
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.
1114
- obj: an Accessible object that implements the AccessibleText
1116
- word: the word to speak
1117
- startOffset: the start offset for this word
1118
- endOffset: the end offset for this word
1121
voices = _settingsManager.getSetting('voices')
1123
for i in range(startOffset, endOffset):
1124
if self.utilities.linkIndex(obj, i) >= 0:
1125
voice = voices[settings.HYPERLINK_VOICE]
1127
elif word.isupper():
1128
voice = voices[settings.UPPERCASE_VOICE]
1130
voice = voices[settings.DEFAULT_VOICE]
1132
speech.speak(word, voice)
1133
if self.endOfLink(obj, word, startOffset, endOffset):
1134
speech.speak(messages.LINK)
1136
def speakSetupLabel(self, label):
1137
"""Speak this Setup dialog label.
1140
- label: the Setup dialog Label.
1143
text = self.utilities.displayedText(label)
1147
def handleSetupPanel(self, panel):
1148
"""Find all the labels in this Setup panel and speak them.
1151
- panel: the Setup panel.
1154
allLabels = self.utilities.descendantsWithRole(
1155
panel, pyatspi.ROLE_LABEL)
1156
for label in allLabels:
1157
self.speakSetupLabel(label)
1159
def _speakWriterText(self, event, textToSpeak):
1160
"""Called to speak the current line or paragraph of Writer text.
1164
- textToSpeak: the text to speak
1167
if not textToSpeak and event and self.speakBlankLine(event.source):
1168
speech.speak(messages.BLANK, None, False)
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.
1176
hypertext = event.source.queryHypertext()
1177
except NotImplementedError:
1180
if not hypertext or (hypertext.getNLinks() == 0):
1181
result = self.speechGenerator.generateTextIndentation(
1182
event.source, line=textToSpeak)
1184
speech.speak(result[0])
1186
speech.speak(textToSpeak, None, False)
1190
for i in range(0, len(textToSpeak)):
1191
if textToSpeak[i] == ' ':
1194
self.sayWriterWord(event.source,
1195
textToSpeak[startOffset:endOffset+1],
1196
startOffset, endOffset)
1205
endOffset = len(textToSpeak)
1206
self.sayWriterWord(event.source,
1207
textToSpeak[startOffset:endOffset], startOffset, endOffset)
1209
701
def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
1210
702
"""Called when the visual object with focus changes.
1324
739
self.speechGenerator.generateSpeech(tab)
1325
740
speech.speak(utterances)
1326
# Fall-thru to process the event with the default handler.
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
1333
rolesList = [[pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_LIST_ITEM],
1334
[pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
1335
pyatspi.ROLE_SCROLL_PANE,
1338
pyatspi.ROLE_ROOT_PANE,
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),
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.
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):
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.
1367
state = newLocusOfFocus.parent[0].getState()
1368
if not state.contains(pyatspi.STATE_EDITABLE):
1369
newLocusOfFocus = newLocusOfFocus.parent
1371
newLocusOfFocus = newLocusOfFocus.parent[0]
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)
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)
764
text = newLocusOfFocus.queryText()
768
self._saveLastCursorPosition(newLocusOfFocus, text.caretOffset)
1373
771
# Pass the event onto the parent class to be handled in the default way.
1375
772
default.Script.locusOfFocusChanged(self, event,
1376
773
oldLocusOfFocus, newLocusOfFocus)
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
1378
781
def onWindowActivated(self, event):
1379
782
"""Called whenever a property on an object changes.
1625
902
briefMessage = messages.TABLE_ROW_INSERTED
1627
904
self.presentMessage(fullMessage, briefMessage, voice)
1629
def onStateChanged(self, event):
1630
"""Called whenever an object's state changes.
1636
if event.source.getRole() == pyatspi.ROLE_EXTENDED:
1637
if event.source.getRoleName() == 'text frame':
907
default.Script.onChildrenChanged(self, event)
909
def onFocus(self, event):
910
"""Callback for focus: accessibility events."""
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.
916
role = event.source.getRole()
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)
924
if role == pyatspi.ROLE_PUSH_BUTTON:
925
orca.setLocusOfFocus(event, event.source)
929
if role == pyatspi.ROLE_COMBO_BOX:
930
orca.setLocusOfFocus(event, event.source)
933
def onFocusedChanged(self, event):
934
"""Callback for object:state-changed:focused accessibility events."""
936
if self.isStructuralNavigationCommand():
939
if not event.detail1:
942
if event.source.getRoleName() == 'text frame':
945
ignoreRoles = [pyatspi.ROLE_FILLER, pyatspi.ROLE_PANEL]
946
if event.source.getRole() in ignoreRoles:
1640
949
parent = event.source.parent
1641
if parent and parent.getRole() == pyatspi.ROLE_EXTENDED:
1642
if parent.getRoleName() == 'text frame':
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.
1648
if event.type.startswith("object:state-changed:focused") \
1649
and (not event.source.getState().contains(pyatspi.STATE_FOCUSED) \
1650
or event.detail1 == 0):
1653
# Prevent "object:state-changed:active" events from activating
1654
# the find operation. See comment #18 of bug #354463.
1656
if event.type.startswith("object:state-changed:active"):
1657
if self.findCommandRun:
950
if parent and parent.getRoleName() == 'text frame':
953
obj, offset = self.pointOfReference.get("lastCursorPosition", (None, -1))
954
textSelections = self.pointOfReference.get('textSelections', {})
955
start, end = textSelections.get(hash(obj), (0, 0))
959
if self.utilities._flowsFromOrToSelection(event.source):
962
# We should present this in response to active-descendant-changed events
963
if event.source.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS):
966
default.Script.onFocusedChanged(self, event)
968
def onCaretMoved(self, event):
969
"""Called whenever the caret moves.
975
if self.isStructuralNavigationCommand():
978
if event.detail1 == -1:
981
if self.utilities.isCellBeingEdited(event.source):
982
orca.setLocusOfFocus(event, event.source.parent, False)
984
if not orca_state.locusOfFocus:
985
default.Script.onCaretMoved(self, event)
988
if orca_state.locusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL:
989
default.Script.onCaretMoved(self, event)
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
1000
if event.source.getRole() == pyatspi.ROLE_TEXT \
1001
and self.utilities.ancestorWithRole(
1003
[pyatspi.ROLE_TOOL_BAR, pyatspi.ROLE_DIALOG],
1004
[pyatspi.ROLE_FRAME]):
1005
orca.setLocusOfFocus(event, event.source, False)
1007
if self.utilities._flowsFromOrToSelection(event.source):
1010
default.Script.onCaretMoved(self, event)
1012
def onCheckedChanged(self, event):
1013
"""Callback for object:state-changed:checked accessibility events."""
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)
1660
1023
# Announce when the toolbar buttons are toggled if we just toggled
1661
1024
# them; not if we navigated to some text.
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):
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)
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
1681
speech.speak(self.speechGenerator.generateSpeech(event.source))
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.
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,
1698
pyatspi.ROLE_ROOT_PANE,
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)
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).
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)
1717
default.Script.onStateChanged(self, event)
1719
def onSelectionChanged(self, event):
1720
"""Called when an object's selection changes.
1726
details = debug.getAccessibleDetails(self.debugLevel, event.source)
1727
debug.printObjectEvent(self.debugLevel, event, details)
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.
1735
rolesList = [pyatspi.ROLE_LIST,
1736
pyatspi.ROLE_COMBO_BOX,
1738
pyatspi.ROLE_TOOL_BAR,
1740
pyatspi.ROLE_ROOT_PANE,
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
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.
1753
if cell != self.lastCell[0]:
1754
self.lastCell[0] = cell
1757
if cell.queryText():
1758
cellText = self.utilities.substring(cell, 0, -1)
1759
if cellText and len(cellText):
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,
1770
self.presentItemsInBraille([hf])
1772
# Fall-thru to process the event
1773
# with the default handler.
1774
except NotImplementedError:
1776
except NotImplementedError:
1779
default.Script.onSelectionChanged(self, event)
1781
def speakCellName(self, name):
1782
"""Speaks the given cell name.
1785
- name: the name of the cell
1788
line = messages.CELL % name
1791
def onCaretMoved(self, event):
1792
"""Called whenever the caret moves.
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.
1802
if self.isStructuralNavigationCommand():
1805
if self.utilities.isDuplicateEvent(event):
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.
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,
1824
pyatspi.ROLE_ROOT_PANE,
1826
pyatspi.ROLE_APPLICATION]
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,
1833
pyatspi.ROLE_ROOT_PANE,
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))
1846
# Otherwise, if the object is losing focus, then just ignore this event.
1848
if event.detail1 == -1:
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)
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.
1862
self.lastCell[1] = event.detail1
1864
event_string, mods = self.utilities.lastKeyAndModifiers()
1865
isControlKey = mods & settings.CTRL_MODIFIER_MASK
1866
isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
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
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.
1879
eventIndex = event.source.getIndexInParent()
1880
if self.currentParagraph:
1881
paraIndex = self.currentParagraph.getIndexInParent()
1883
paraIndex = eventIndex
1885
if (event_string == "Down" and (eventIndex - paraIndex <= 0)) \
1886
or (event_string == "Up" and (eventIndex - paraIndex >= 0)):
1889
result = self.utilities.substring(event.source, 0, -1)
1890
self._speakWriterText(event, result)
1891
self.displayBrailleForObject(event.source)
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
1901
if event.source.getRole() == pyatspi.ROLE_TEXT \
1902
and self.utilities.ancestorWithRole(
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)
1909
# If we're still here, we must be convinced that this paragraph
1910
# coincides with our actual location.
1912
self.currentParagraph = event.source
1914
def speakBlankLine(self, obj):
1915
"""Returns True if a blank line should be spoken.
1916
Otherwise, returns False.
1919
# Get the the AccessibleText interface.
1921
text = obj.queryText()
1922
except NotImplementedError:
1925
# Get the line containing the caret
1926
caretOffset = text.caretOffset
1927
line = text.getTextAtOffset(caretOffset, \
1928
pyatspi.TEXT_BOUNDARY_LINE_START)
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')
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
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.
1948
text = event.any_data
1949
if isinstance(orca_state.lastInputEvent,
1950
input_event.MouseButtonEvent) and \
1951
orca_state.lastInputEvent.button == "2":
1953
speech.speak(text, self.voices[settings.UPPERCASE_VOICE])
1957
default.Script.onTextInserted(self, event)
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)
1031
keyString, mods = self.utilities.lastKeyAndModifiers()
1032
navKeys = ["Up", "Down", "Left", "Right", "Page_Up", "Page_Down",
1034
wasCommand = mods & settings.COMMAND_MODIFIER_MASK
1035
weToggledIt = wasCommand and keyString not in navKeys
1037
speech.speak(self.speechGenerator.generateSpeech(obj))
1039
def onRowReordered(self, event):
1040
"""Callback for object:row-reordered accessibility events."""
1042
# We're seeing a crazy ton of these emitted bogusly.
1045
def onTextAttributesChanged(self, event):
1046
"""Callback for object:text-attributes-changed accessibility events."""
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
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
1056
# Useless signals are useless.
1959
1059
def getTextLineAtCaret(self, obj, offset=None):
1960
1060
"""Gets the line of text where the caret is. Overridden here to