Package NVDAObjects :: Package window :: Module winword
[hide private]
[frames] | no frames]

Source Code for Module NVDAObjects.window.winword

  1  #appModules/winword.py 
  2  #A part of NonVisual Desktop Access (NVDA) 
  3  #Copyright (C) 2006-2007 NVDA Contributors <http://www.nvda-project.org/> 
  4  #This file is covered by the GNU General Public License. 
  5  #See the file COPYING for more details. 
  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  #Word constants 
 25   
 26  wdCollapseEnd=0 
 27  wdCollapseStart=1 
 28  #Indexing 
 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  #Horizontal alignment 
 39  wdAlignParagraphLeft=0 
 40  wdAlignParagraphCenter=1 
 41  wdAlignParagraphRight=2 
 42  wdAlignParagraphJustify=3 
 43  #Units 
 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  #GoTo - direction 
 58  wdGoToAbsolute=1 
 59  wdGoToRelative=2 
 60  wdGoToNext=2 
 61  wdGoToPrevious=3 
 62  #GoTo - units 
 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
106 - def _expandToLineAtCaret(self):
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
114 - def _getFormatFieldAtRange(self,range,formatConfig):
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
164 - def _expandFormatRange(self,range):
165 startLimit=self._rangeObj.start 166 endLimit=self._rangeObj.end 167 #Only Office 2007 onwards supports moving by format changes, -- and only moveEnd works. 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 #Trying to set the start past the end of the document forces both start and end back to the previous offset, so catch this 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
270 - def _get_isCollapsed(self):
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
283 - def copy(self):
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 #units higher than character and word expand to contain the last text plus the insertion point offset in the document 307 #However move from a character before will incorrectly move to this offset which makes move/expand contridictory to each other 308 #Make sure that move fails if it lands on the final offset but the unit is bigger than character/word 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
328 - def __init__(self,*args,**kwargs):
329 super(WordDocument,self).__init__(*args,**kwargs)
330
331 - def _get_role(self):
333
334 - def _get_WinwordVersion(self):
335 if not hasattr(self,'_WinwordVersion'): 336 self._WinwordVersion=float(self.WinwordWindowObject.application.version) 337 return self._WinwordVersion
338
339 - def _get_WinwordWindowObject(self):
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
368 - def script_tab(self,gesture):
369 gesture.send() 370 info=self.makeTextInfo(textInfos.POSITION_CARET) 371 if info._rangeObj.tables.count>0: 372 info.expand(textInfos.UNIT_LINE) 373 speech.speakTextInfo(info,reason=speech.REASON_CARET)
374
375 - def script_nextRow(self,gesture):
376 info=self.makeTextInfo("caret") 377 if not info._rangeObj.Information(wdWithInTable): 378 speech.speakMessage(_("not in table")) 379 return 380 if info._moveInTable(0,1): 381 info.updateCaret() 382 info.expand(textInfos.UNIT_CELL) 383 speech.speakTextInfo(info,reason=speech.REASON_CARET) 384 else: 385 speech.speakMessage(_("edge of table"))
386
387 - def script_previousRow(self,gesture):
388 info=self.makeTextInfo("caret") 389 if not info._rangeObj.Information(wdWithInTable): 390 speech.speakMessage(_("not in table")) 391 return 392 if info._moveInTable(0,-1): 393 info.updateCaret() 394 info.expand(textInfos.UNIT_CELL) 395 speech.speakTextInfo(info,reason=speech.REASON_CARET) 396 else: 397 speech.speakMessage(_("edge of table"))
398
399 - def script_nextColumn(self,gesture):
400 info=self.makeTextInfo("caret") 401 if not info._rangeObj.Information(wdWithInTable): 402 speech.speakMessage(_("not in table")) 403 return 404 if info._moveInTable(1,0): 405 info.updateCaret() 406 info.expand(textInfos.UNIT_CELL) 407 speech.speakTextInfo(info,reason=speech.REASON_CARET) 408 else: 409 speech.speakMessage(_("edge of table"))
410
411 - def script_previousColumn(self,gesture):
412 info=self.makeTextInfo("caret") 413 if not info._rangeObj.Information(wdWithInTable): 414 speech.speakMessage(_("not in table")) 415 return 416 if info._moveInTable(-1,0): 417 info.updateCaret() 418 info.expand(textInfos.UNIT_CELL) 419 speech.speakTextInfo(info,reason=speech.REASON_CARET) 420 else: 421 speech.speakMessage(_("edge of table"))
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