1
2
3
4
5
6
7 import ctypes
8 from comtypes import COMError, GUID
9 import comtypes.client
10 import comtypes.automation
11 import NVDAHelper
12 from logHandler import log
13 import winUser
14 import oleacc
15 import globalVars
16 import speech
17 import config
18 import textInfos
19 import textInfos.offsets
20 import controlTypes
21 from . import Window
22 from ..behaviors import EditableTextWithoutAutoSelectDetection
23
24
25
26 wdCollapseEnd=0
27 wdCollapseStart=1
28
29 wdActiveEndAdjustedPageNumber=1
30 wdActiveEndPageNumber=3
31 wdNumberOfPagesInDocument=4
32 wdFirstCharacterLineNumber=10
33 wdWithInTable=12
34 wdStartOfRangeRowNumber=13
35 wdMaximumNumberOfRows=15
36 wdStartOfRangeColumnNumber=16
37 wdMaximumNumberOfColumns=18
38
39 wdAlignParagraphLeft=0
40 wdAlignParagraphCenter=1
41 wdAlignParagraphRight=2
42 wdAlignParagraphJustify=3
43
44 wdCharacter=1
45 wdWord=2
46 wdSentence=3
47 wdParagraph=4
48 wdLine=5
49 wdStory=6
50 wdColumn=9
51 wdRow=10
52 wdWindow=11
53 wdCell=12
54 wdCharFormat=13
55 wdParaFormat=14
56 wdTable=15
57
58 wdGoToAbsolute=1
59 wdGoToRelative=2
60 wdGoToNext=2
61 wdGoToPrevious=3
62
63 wdGoToPage=1
64 wdGoToLine=3
65
66 winwordWindowIid=GUID('{00020962-0000-0000-C000-000000000046}')
67
68 wm_winword_expandToLine=ctypes.windll.user32.RegisterWindowMessageW(u"wm_winword_expandToLine")
69
70 NVDAUnitsToWordUnits={
71 textInfos.UNIT_CHARACTER:wdCharacter,
72 textInfos.UNIT_WORD:wdWord,
73 textInfos.UNIT_LINE:wdLine,
74 textInfos.UNIT_SENTENCE:wdSentence,
75 textInfos.UNIT_PARAGRAPH:wdParagraph,
76 textInfos.UNIT_TABLE:wdTable,
77 textInfos.UNIT_CELL:wdCell,
78 textInfos.UNIT_ROW:wdRow,
79 textInfos.UNIT_COLUMN:wdColumn,
80 textInfos.UNIT_STORY:wdStory,
81 textInfos.UNIT_READINGCHUNK:wdSentence,
82 }
83
84 -class WordDocumentTextInfo(textInfos.TextInfo):
85
86 - def _moveInTable(self,c=0,r=0):
87 try:
88 cell=self._rangeObj.cells[1]
89 except:
90 return False
91 try:
92 columnIndex=cell.columnIndex
93 rowIndex=cell.rowIndex
94 except:
95 return False
96 if columnIndex==1 and c<0:
97 return False
98 if rowIndex==1 and r<0:
99 return False
100 try:
101 self._rangeObj=self._rangeObj.tables[1].columns[columnIndex+c].cells[rowIndex+r].range
102 except:
103 return False
104 return True
105
107 lineStart=ctypes.c_int()
108 lineEnd=ctypes.c_int()
109 res=NVDAHelper.localLib.nvdaInProcUtils_winword_expandToLine(self.obj.appModule.helperLocalBindingHandle,self.obj.windowHandle,self._rangeObj.start,ctypes.byref(lineStart),ctypes.byref(lineEnd))
110 if res!=0:
111 raise ctypes.WinError(res)
112 self._rangeObj.setRange(lineStart.value,lineEnd.value)
113
115 formatField=textInfos.FormatField()
116 fontObj=None
117 paraFormatObj=None
118 listString=range.ListFormat.ListString
119 if listString and range.Paragraphs[1].range.start==range.start:
120 formatField['line-prefix']=listString
121 if formatConfig["reportSpellingErrors"] and range.spellingErrors.count>0:
122 formatField["invalid-spelling"]=True
123 if formatConfig["reportLineNumber"]:
124 formatField["line-number"]=range.Information(wdFirstCharacterLineNumber)
125 if formatConfig["reportPage"]:
126 formatField["page-number"]=range.Information(wdActiveEndAdjustedPageNumber)
127 if formatConfig["reportStyle"]:
128 formatField["style"]=range.style.nameLocal
129 if formatConfig["reportTables"] and range.Information(wdWithInTable):
130 tableInfo={}
131 tableInfo["column-count"]=range.Information(wdMaximumNumberOfColumns)
132 tableInfo["row-count"]=range.Information(wdMaximumNumberOfRows)
133 tableInfo["column-number"]=range.Information(wdStartOfRangeColumnNumber)
134 tableInfo["row-number"]=range.Information(wdStartOfRangeRowNumber)
135 formatField["table-info"]=tableInfo
136 if formatConfig["reportAlignment"]:
137 if not paraFormatObj: paraFormatObj=range.paragraphFormat
138 alignment=paraFormatObj.alignment
139 if alignment==wdAlignParagraphLeft:
140 formatField["text-align"]="left"
141 elif alignment==wdAlignParagraphCenter:
142 formatField["text-align"]="center"
143 elif alignment==wdAlignParagraphRight:
144 formatField["text-align"]="right"
145 elif alignment==wdAlignParagraphJustify:
146 formatField["text-align"]="justify"
147 if formatConfig["reportFontName"]:
148 if not fontObj: fontObj=range.font
149 formatField["font-name"]=fontObj.name
150 if formatConfig["reportFontSize"]:
151 if not fontObj: fontObj=range.font
152 formatField["font-size"]="%spt"%fontObj.size
153 if formatConfig["reportFontAttributes"]:
154 if not fontObj: fontObj=range.font
155 formatField["bold"]=bool(fontObj.bold)
156 formatField["italic"]=bool(fontObj.italic)
157 formatField["underline"]=bool(fontObj.underline)
158 if fontObj.superscript:
159 formatField["text-position"]="super"
160 elif fontObj.subscript:
161 formatField["text-position"]="sub"
162 return formatField
163
165 startLimit=self._rangeObj.start
166 endLimit=self._rangeObj.end
167
168 try:
169 range.MoveEnd(13,1)
170 except:
171 range.Expand(wdWord)
172 if range.start<startLimit:
173 range.start=startLimit
174 if range.end>endLimit:
175 range.end=endLimit
176
177 - def __init__(self,obj,position,_rangeObj=None):
178 super(WordDocumentTextInfo,self).__init__(obj,position)
179 if _rangeObj:
180 self._rangeObj=_rangeObj.Duplicate
181 return
182 if isinstance(position,textInfos.Point):
183 self._rangeObj=self.obj.WinwordDocumentObject.activeWindow.RangeFromPoint(position.x,position.y)
184 elif position==textInfos.POSITION_SELECTION:
185 self._rangeObj=self.obj.WinwordSelectionObject.range
186 elif position==textInfos.POSITION_CARET:
187 self._rangeObj=self.obj.WinwordSelectionObject.range
188 self._rangeObj.Collapse()
189 elif position==textInfos.POSITION_ALL:
190 self._rangeObj=self.obj.WinwordSelectionObject.range
191 self._rangeObj.Expand(wdStory)
192 elif position==textInfos.POSITION_FIRST:
193 self._rangeObj=self.obj.WinwordSelectionObject.range
194 self._rangeObj.SetRange(0,0)
195 elif position==textInfos.POSITION_LAST:
196 self._rangeObj=self.obj.WinwordSelectionObject.range
197 self._rangeObj.moveEnd(wdStory,1)
198 self._rangeObj.move(wdCharacter,-1)
199 elif isinstance(position,textInfos.offsets.Offsets):
200 self._rangeObj=self.obj.WinwordSelectionObject.range
201 self._rangeObj.SetRange(position.startOffset,position.endOffset)
202 else:
203 raise NotImplementedError("position: %s"%position)
204
205 - def getTextWithFields(self,formatConfig=None):
206 if not formatConfig:
207 formatConfig=config.conf["documentFormatting"]
208 range=self._rangeObj.duplicate
209 range.Collapse()
210 if not formatConfig["detectFormatAfterCursor"]:
211 range.expand(wdCharacter)
212 field=textInfos.FieldCommand("formatChange",self._getFormatFieldAtRange(range,formatConfig))
213 return [field,self.text]
214 commandList=[]
215 endLimit=self._rangeObj.end
216 range=self._rangeObj.duplicate
217 range.Collapse()
218 while range.end<endLimit:
219 self._expandFormatRange(range)
220 commandList.append(textInfos.FieldCommand("formatChange",self._getFormatFieldAtRange(range,formatConfig)))
221 commandList.append(range.text)
222 end=range.end
223 range.start=end
224
225 if range.end<end:
226 break
227 return commandList
228
229 - def expand(self,unit):
230 if unit==textInfos.UNIT_LINE and self.basePosition not in (textInfos.POSITION_CARET,textInfos.POSITION_SELECTION):
231 unit=textInfos.UNIT_SENTENCE
232 if unit==textInfos.UNIT_LINE:
233 self._expandToLineAtCaret()
234 elif unit==textInfos.UNIT_CHARACTER:
235 self._rangeObj.moveEnd(wdCharacter,1)
236 elif unit in NVDAUnitsToWordUnits:
237 self._rangeObj.Expand(NVDAUnitsToWordUnits[unit])
238 else:
239 raise NotImplementedError("unit: %s"%unit)
240
241 - def compareEndPoints(self,other,which):
242 if which=="startToStart":
243 diff=self._rangeObj.Start-other._rangeObj.Start
244 elif which=="startToEnd":
245 diff=self._rangeObj.Start-other._rangeObj.End
246 elif which=="endToStart":
247 diff=self._rangeObj.End-other._rangeObj.Start
248 elif which=="endToEnd":
249 diff=self._rangeObj.End-other._rangeObj.End
250 else:
251 raise ValueError("bad argument - which: %s"%which)
252 if diff<0:
253 diff=-1
254 elif diff>0:
255 diff=1
256 return diff
257
258 - def setEndPoint(self,other,which):
259 if which=="startToStart":
260 self._rangeObj.Start=other._rangeObj.Start
261 elif which=="startToEnd":
262 self._rangeObj.Start=other._rangeObj.End
263 elif which=="endToStart":
264 self._rangeObj.End=other._rangeObj.Start
265 elif which=="endToEnd":
266 self._rangeObj.End=other._rangeObj.End
267 else:
268 raise ValueError("bad argument - which: %s"%which)
269
271 if self._rangeObj.Start==self._rangeObj.End:
272 return True
273 else:
274 return False
275
276 - def collapse(self,end=False):
277 if end:
278 oldEndOffset=self._rangeObj.end
279 self._rangeObj.collapse(wdCollapseEnd if end else wdCollapseStart)
280 if end and self._rangeObj.end<oldEndOffset:
281 raise RuntimeError
282
284 return WordDocumentTextInfo(self.obj,None,_rangeObj=self._rangeObj)
285
286 - def _get_text(self):
287 text=self._rangeObj.text
288 if not text:
289 text=""
290 return text
291
292 - def move(self,unit,direction,endPoint=None):
293 if unit==textInfos.UNIT_LINE:
294 unit=textInfos.UNIT_SENTENCE
295 if unit in NVDAUnitsToWordUnits:
296 unit=NVDAUnitsToWordUnits[unit]
297 else:
298 raise NotImplementedError("unit: %s"%unit)
299 if endPoint=="start":
300 moveFunc=self._rangeObj.MoveStart
301 elif endPoint=="end":
302 moveFunc=self._rangeObj.MoveEnd
303 else:
304 moveFunc=self._rangeObj.Move
305 res=moveFunc(unit,direction)
306
307
308
309 if direction>0 and endPoint!="end" and unit not in (wdCharacter,wdWord) and (self._rangeObj.start+1)==self.obj.WinwordDocumentObject.characters.count:
310 return 0
311 return res
312
313 - def _get_bookmark(self):
314 return textInfos.offsets.Offsets(self._rangeObj.Start,self._rangeObj.End)
315
316 - def updateCaret(self):
317 self.obj.WinwordWindowObject.ScrollIntoView(self._rangeObj)
318 self.obj.WinwordSelectionObject.SetRange(self._rangeObj.Start,self._rangeObj.Start)
319
320 - def updateSelection(self):
321 self.obj.WinwordWindowObject.ScrollIntoView(self._rangeObj)
322 self.obj.WinwordSelectionObject.SetRange(self._rangeObj.Start,self._rangeObj.End)
323
324 -class WordDocument(EditableTextWithoutAutoSelectDetection, Window):
325
326 TextInfo=WordDocumentTextInfo
327
330
333
335 if not hasattr(self,'_WinwordVersion'):
336 self._WinwordVersion=float(self.WinwordWindowObject.application.version)
337 return self._WinwordVersion
338
340 if not getattr(self,'_WinwordWindowObject',None):
341 try:
342 pDispatch=oleacc.AccessibleObjectFromWindow(self.windowHandle,winUser.OBJID_NATIVEOM,interface=comtypes.automation.IDispatch)
343 except (COMError, WindowsError):
344 log.debugWarning("Could not get MS Word object model",exc_info=True)
345 return None
346 self._WinwordWindowObject=comtypes.client.dynamic.Dispatch(pDispatch)
347 return self._WinwordWindowObject
348
350 if not getattr(self,'_WinwordDocumentObject',None):
351 windowObject=self.WinwordWindowObject
352 if not windowObject: return None
353 self._WinwordDocumentObject=windowObject.document
354 return self._WinwordDocumentObject
355
357 if not getattr(self,'_WinwordApplicationObject',None):
358 self._WinwordApplicationObject=self.WinwordWindowObject.application
359 return self._WinwordApplicationObject
360
362 if not getattr(self,'_WinwordSelectionObject',None):
363 windowObject=self.WinwordWindowObject
364 if not windowObject: return None
365 self._WinwordSelectionObject=windowObject.selection
366 return self._WinwordSelectionObject
367
374
386
398
410
422
423 __gestures = {
424 "kb:tab": "tab",
425 "kb:shift+tab": "tab",
426 "kb:control+alt+upArrow": "previousRow",
427 "kb:control+alt+downArrow": "nextRow",
428 "kb:control+alt+leftArrow": "previousColumn",
429 "kb:control+alt+rightArrow": "nextColumn",
430 "kb:control+pageUp": "caret_moveByLine",
431 "kb:control+pageDown": "caret_moveByLine",
432 }
433