soffice.py
8.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#appModules/soffice.py
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2006-2010 Michael Curran <mick@kulgan.net>, James Teh <jamie@jantrid.net>
from comtypes import COMError
import IAccessibleHandler
import appModuleHandler
import controlTypes
import textInfos
import colors
from compoundDocuments import CompoundDocument
from NVDAObjects.JAB import JAB, JABTextInfo
from NVDAObjects.IAccessible import IAccessible, IA2TextTextInfo
from NVDAObjects.behaviors import EditableText
from logHandler import log
def gridCoordStringToNumbers(coordString):
if not coordString or len(coordString)<2 or ' ' in coordString or coordString[0].isdigit() or not coordString[-1].isdigit():
raise ValueError("bad coord string: %r"%coordString)
rowNum=0
colNum=0
coordStringRowStartIndex=None
for index,ch in enumerate(reversed(coordString)):
if not ch.isdigit():
coordStringRowStartIndex=len(coordString)-index
break
rowNum=int(coordString[coordStringRowStartIndex:])
for index,ch in enumerate(reversed(coordString[0:coordStringRowStartIndex])):
colNum+=((ord(ch.upper())-ord('A')+1)*(26**index))
return rowNum,colNum
class JAB_OOTable(JAB):
def _get_rowCount(self):
return 0
def _get_columnCount(self):
return 0
class JAB_OOTableCell(JAB):
role=controlTypes.ROLE_TABLECELL
def _get_name(self):
name=super(JAB_OOTableCell,self).name
if name and name.startswith('Cell') and name[-2].isdigit():
return None
return name
def _get_cellCoordsText(self):
name=super(JAB_OOTableCell,self).name
if name and name.startswith('Cell') and name[-2].isdigit():
return name[5:-1]
def _get_value(self):
value=super(JAB_OOTableCell,self).value
if not value and issubclass(self.TextInfo,JABTextInfo):
value=self.makeTextInfo(textInfos.POSITION_ALL).text
return value
def _get_states(self):
states=super(JAB_OOTableCell,self).states
states.discard(controlTypes.STATE_EDITABLE)
return states
def _get_rowNumber(self):
try:
return gridCoordStringToNumbers(self.cellCoordsText)[0]
except ValueError:
return 0
def _get_columnNumber(self):
try:
return gridCoordStringToNumbers(self.cellCoordsText)[1]
except ValueError:
return 0
class SymphonyTextInfo(IA2TextTextInfo):
def _getFormatFieldAndOffsets(self,offset,formatConfig,calculateOffsets=True):
obj = self.obj
try:
startOffset,endOffset,attribsString=obj.IAccessibleTextObject.attributes(offset)
except COMError:
log.debugWarning("could not get attributes",exc_info=True)
return textInfos.FormatField(),(self._startOffset,self._endOffset)
formatField=textInfos.FormatField()
if not attribsString and offset>0:
try:
attribsString=obj.IAccessibleTextObject.attributes(offset-1)[2]
except COMError:
pass
if attribsString:
formatField.update(IAccessibleHandler.splitIA2Attribs(attribsString))
try:
escapement = int(formatField["CharEscapement"])
if escapement < 0:
textPos = "sub"
elif escapement > 0:
textPos = "super"
else:
textPos = "baseline"
formatField["text-position"] = textPos
except KeyError:
pass
try:
formatField["font-name"] = formatField["CharFontName"]
except KeyError:
pass
try:
formatField["font-size"] = "%spt" % formatField["CharHeight"]
except KeyError:
pass
try:
formatField["italic"] = formatField["CharPosture"] == "2"
except KeyError:
pass
try:
formatField["strikethrough"] = formatField["CharStrikeout"] == "1"
except KeyError:
pass
try:
underline = formatField["CharUnderline"]
if underline == "10":
# Symphony doesn't provide for semantic communication of spelling errors, so we have to rely on the WAVE underline type.
formatField["invalid-spelling"] = True
else:
formatField["underline"] = underline != "0"
except KeyError:
pass
try:
formatField["bold"] = float(formatField["CharWeight"]) > 100
except KeyError:
pass
try:
color=formatField.pop('CharColor')
except KeyError:
color=None
if color:
formatField['color']=colors.RGB.fromString(color)
try:
backgroundColor=formatField.pop('CharBackColor')
except KeyError:
backgroundColor=None
if backgroundColor:
formatField['background-color']=colors.RGB.fromString(backgroundColor)
# optimisation: Assume a hyperlink occupies a full attribute run.
try:
if obj.IAccessibleTextObject.QueryInterface(IAccessibleHandler.IAccessibleHypertext).hyperlinkIndex(offset) != -1:
formatField["link"] = True
except COMError:
pass
if offset == 0:
# Only include the list item prefix on the first line of the paragraph.
numbering = formatField.get("Numbering")
if numbering:
formatField["line-prefix"] = numbering.get("NumberingPrefix") or numbering.get("BulletChar")
if obj.hasFocus:
# Symphony exposes some information for the caret position as attributes on the document object.
# optimisation: Use the tree interceptor to get the document.
try:
docAttribs = obj.treeInterceptor.rootNVDAObject.IA2Attributes
except AttributeError:
# No tree interceptor, so we can't efficiently fetch this info.
pass
else:
try:
formatField["page-number"] = docAttribs["page-number"]
except KeyError:
pass
try:
formatField["line-number"] = docAttribs["line-number"]
except KeyError:
pass
return formatField,(startOffset,endOffset)
def _getLineOffsets(self, offset):
start, end = super(SymphonyTextInfo, self)._getLineOffsets(offset)
if offset == 0 and start == 0 and end == 0:
# HACK: Symphony doesn't expose any characters at all on empty lines, but this means we don't ever fetch the list item prefix in this case.
# Fake a character so that the list item prefix will be spoken on empty lines.
return (0, 1)
return start, end
def _getStoryLength(self):
# HACK: Account for the character faked in _getLineOffsets() so that move() will work.
return max(super(SymphonyTextInfo, self)._getStoryLength(), 1)
class SymphonyText(IAccessible, EditableText):
TextInfo = SymphonyTextInfo
def _get_positionInfo(self):
level = self.IA2Attributes.get("heading-level")
if level:
return {"level": int(level)}
return super(SymphonyText, self).positionInfo
class SymphonyTableCell(IAccessible):
"""Silences particular states, and redundant column/row numbers"""
TextInfo=SymphonyTextInfo
def _get_cellCoordsText(self):
return super(SymphonyTableCell,self).name
name=None
def _get_states(self):
states=super(SymphonyTableCell,self).states
states.discard(controlTypes.STATE_MULTILINE)
states.discard(controlTypes.STATE_EDITABLE)
return states
class SymphonyParagraph(SymphonyText):
"""Removes redundant information that can be retreaved in other ways."""
value=None
description=None
class AppModule(appModuleHandler.AppModule):
def chooseNVDAObjectOverlayClasses(self, obj, clsList):
role=obj.role
windowClassName=obj.windowClassName
if isinstance(obj, IAccessible) and windowClassName in ("SALTMPSUBFRAME", "SALSUBFRAME", "SALFRAME"):
if role==controlTypes.ROLE_TABLECELL:
clsList.insert(0, SymphonyTableCell)
elif hasattr(obj, "IAccessibleTextObject"):
clsList.insert(0, SymphonyText)
if role==controlTypes.ROLE_PARAGRAPH:
clsList.insert(0, SymphonyParagraph)
if isinstance(obj, JAB) and windowClassName == "SALFRAME":
if role in (controlTypes.ROLE_PANEL,controlTypes.ROLE_LABEL):
parent=obj.parent
if parent and parent.role==controlTypes.ROLE_TABLE:
clsList.insert(0,JAB_OOTableCell)
elif role==controlTypes.ROLE_TABLE:
clsList.insert(0,JAB_OOTable)
def event_NVDAObject_init(self, obj):
windowClass = obj.windowClassName
if isinstance(obj, JAB) and windowClass == "SALFRAME":
# OpenOffice.org has some strange role mappings due to its use of JAB.
if obj.role == controlTypes.ROLE_CANVAS:
obj.role = controlTypes.ROLE_DOCUMENT
if windowClass in ("SALTMPSUBFRAME", "SALFRAME") and obj.role in (controlTypes.ROLE_DOCUMENT,controlTypes.ROLE_TEXTFRAME) and obj.description:
# This is a word processor document.
obj.description = None
obj.treeInterceptorClass = CompoundDocument