#appModules/winword.py #A part of NonVisual Desktop Access (NVDA) #Copyright (C) 2006-2016 NV Access Limited, Manish Agrawal, Derek Riemer #This file is covered by the GNU General Public License. #See the file COPYING for more details. import ctypes import time from comtypes import COMError, GUID, BSTR import comtypes.client import comtypes.automation import uuid import operator import locale import collections import colorsys import sayAllHandler import eventHandler import braille import scriptHandler import languageHandler import ui import NVDAHelper import XMLFormatting from logHandler import log import winUser import oleacc import globalVars import speech import config import textInfos import textInfos.offsets import colors import controlTypes import treeInterceptorHandler import browseMode import review from cursorManager import CursorManager, ReviewCursorManager from tableUtils import HeaderCellInfo, HeaderCellTracker from . import Window from ..behaviors import EditableTextWithoutAutoSelectDetection #Word constants #wdLineSpacing rules wdLineSpaceSingle=0 wdLineSpace1pt5=1 wdLineSpaceDouble=2 wdLineSpaceAtLeast=3 wdLineSpaceExactly=4 wdLineSpaceMultiple=5 # wdMeasurementUnits wdInches=0 wdCentimeters=1 wdMillimeters=2 wdPoints=3 wdPicas=4 wdCollapseEnd=0 wdCollapseStart=1 #Indexing wdActiveEndAdjustedPageNumber=1 wdActiveEndPageNumber=3 wdNumberOfPagesInDocument=4 wdHorizontalPositionRelativeToPage=5 wdFirstCharacterLineNumber=10 wdWithInTable=12 wdStartOfRangeRowNumber=13 wdMaximumNumberOfRows=15 wdStartOfRangeColumnNumber=16 wdMaximumNumberOfColumns=18 #Horizontal alignment wdAlignParagraphLeft=0 wdAlignParagraphCenter=1 wdAlignParagraphRight=2 wdAlignParagraphJustify=3 #Units wdCharacter=1 wdWord=2 wdSentence=3 wdParagraph=4 wdLine=5 wdStory=6 wdColumn=9 wdRow=10 wdWindow=11 wdCell=12 wdCharFormat=13 wdParaFormat=14 wdTable=15 #GoTo - direction wdGoToAbsolute=1 wdGoToRelative=2 wdGoToNext=2 wdGoToPrevious=3 #GoTo - units wdGoToBookmark=-1 wdGoToSection=0 wdGoToPage=1 wdGoToTable=2 wdGoToLine=3 wdGoToFootnote=4 wdGoToEndnote=5 wdGoToComment=6 wdGoToField=7 wdGoToGraphic=8 wdGoToObject=9 wdGoToEquation=10 wdGoToHeading=11 wdGoToPercent=12 wdGoToSpellingError=13 wdGoToGrammaticalError=14 wdGoToProofreadingError=15 wdCommentsStory=4 wdEndnotesStory=3 wdEvenPagesFooterStory=8 wdEvenPagesHeaderStory=6 wdFirstPageFooterStory=11 wdFirstPageHeaderStory=10 wdFootnotesStory=2 wdMainTextStory=1 wdPrimaryFooterStory=9 wdPrimaryHeaderStory=7 wdTextFrameStory=5 wdFieldFormTextInput=70 wdFieldFormCheckBox=71 wdFieldFormDropDown=83 wdContentControlRichText=0 wdContentControlText=1 wdContentControlPicture=2 wdContentControlComboBox=3 wdContentControlDropdownList=4 wdContentControlBuildingBlockGallery=5 wdContentControlDate=6 wdContentControlGroup=7 wdContentControlCheckBox=8 wdNoRevision=0 wdRevisionInsert=1 wdRevisionDelete=2 wdRevisionProperty=3 wdRevisionParagraphNumber=4 wdRevisionDisplayField=5 wdRevisionReconcile=6 wdRevisionConflict=7 wdRevisionStyle=8 wdRevisionReplace=9 wdRevisionParagraphProperty=10 wdRevisionTableProperty=11 wdRevisionSectionProperty=12 wdRevisionStyleDefinition=13 wdRevisionMovedFrom=14 wdRevisionMovedTo=15 wdRevisionCellInsertion=16 wdRevisionCellDeletion=17 wdRevisionCellMerge=18 # MsoThemeColorSchemeIndex msoThemeAccent1=5 msoThemeAccent2=6 msoThemeAccent3=7 msoThemeAccent4=8 msoThemeAccent5=9 msoThemeAccent6=10 msoThemeDark1=1 msoThemeDark2=3 msoThemeFollowedHyperlink=12 msoThemeHyperlink=11 msoThemeLight1=2 msoThemeLight2=4 # WdThemeColorIndex wdNotThemeColor=-1 wdThemeColorAccent1=4 wdThemeColorAccent2=5 wdThemeColorAccent3=6 wdThemeColorAccent4=7 wdThemeColorAccent5=8 wdThemeColorAccent6=9 wdThemeColorBackground1=12 wdThemeColorBackground2=14 wdThemeColorHyperlink=10 wdThemeColorHyperlinkFollowed=11 wdThemeColorMainDark1=0 wdThemeColorMainDark2=2 wdThemeColorMainLight1=1 wdThemeColorMainLight2=3 wdThemeColorText1=13 wdThemeColorText2=15 # Word Field types FIELD_TYPE_REF = 3 # cross reference field FIELD_TYPE_HYPERLINK = 88 # hyperlink field # Mapping from http://www.wordarticles.com/Articles/Colours/2007.php#UIConsiderations WdThemeColorIndexToMsoThemeColorSchemeIndex={ wdThemeColorMainDark1:msoThemeDark1, wdThemeColorMainLight1:msoThemeLight1, wdThemeColorMainDark2:msoThemeDark2, wdThemeColorMainLight2:msoThemeLight2, wdThemeColorAccent1:msoThemeAccent1, wdThemeColorAccent2:msoThemeAccent2, wdThemeColorAccent3:msoThemeAccent3, wdThemeColorAccent4:msoThemeAccent4, wdThemeColorAccent5:msoThemeAccent5, wdThemeColorAccent6:msoThemeAccent6, wdThemeColorHyperlink:msoThemeHyperlink, wdThemeColorHyperlinkFollowed:msoThemeFollowedHyperlink, wdThemeColorBackground1:msoThemeLight1, wdThemeColorText1:msoThemeDark1, wdThemeColorBackground2:msoThemeLight2, wdThemeColorText2:msoThemeDark2, } wdRevisionTypeLabels={ # Translators: a Microsoft Word revision type (inserted content) wdRevisionInsert:_("insertion"), # Translators: a Microsoft Word revision type (deleted content) wdRevisionDelete:_("deletion"), # Translators: a Microsoft Word revision type (changed content property, e.g. font, color) wdRevisionProperty:_("property"), # Translators: a Microsoft Word revision type (changed paragraph number) wdRevisionParagraphNumber:_("paragraph number"), # Translators: a Microsoft Word revision type (display field) wdRevisionDisplayField:_("display field"), # Translators: a Microsoft Word revision type (reconcile) wdRevisionReconcile:_("reconcile"), # Translators: a Microsoft Word revision type (conflicting revision) wdRevisionConflict:_("conflict"), # Translators: a Microsoft Word revision type (style change) wdRevisionStyle:_("style"), # Translators: a Microsoft Word revision type (replaced content) wdRevisionReplace:_("replace"), # Translators: a Microsoft Word revision type (changed paragraph property, e.g. alignment) wdRevisionParagraphProperty:_("paragraph property"), # Translators: a Microsoft Word revision type (table) wdRevisionTableProperty:_("table property"), # Translators: a Microsoft Word revision type (section property) wdRevisionSectionProperty:_("section property"), # Translators: a Microsoft Word revision type (style definition) wdRevisionStyleDefinition:_("style definition"), # Translators: a Microsoft Word revision type (moved from) wdRevisionMovedFrom:_("moved from"), # Translators: a Microsoft Word revision type (moved to) wdRevisionMovedTo:_("moved to"), # Translators: a Microsoft Word revision type (inserted table cell) wdRevisionCellInsertion:_("cell insertion"), # Translators: a Microsoft Word revision type (deleted table cell) wdRevisionCellDeletion:_("cell deletion"), # Translators: a Microsoft Word revision type (merged table cells) wdRevisionCellMerge:_("cell merge"), } storyTypeLocalizedLabels={ wdCommentsStory:_("Comments"), wdEndnotesStory:_("Endnotes"), wdEvenPagesFooterStory:_("Even pages footer"), wdEvenPagesHeaderStory:_("Even pages header"), wdFirstPageFooterStory:_("First page footer"), wdFirstPageHeaderStory:_("First page header"), wdFootnotesStory:_("Footnotes"), wdPrimaryFooterStory:_("Primary footer"), wdPrimaryHeaderStory:_("Primary header"), wdTextFrameStory:_("Text frame"), } wdFieldTypesToNVDARoles={ wdFieldFormTextInput:controlTypes.ROLE_EDITABLETEXT, wdFieldFormCheckBox:controlTypes.ROLE_CHECKBOX, wdFieldFormDropDown:controlTypes.ROLE_COMBOBOX, } wdContentControlTypesToNVDARoles={ wdContentControlRichText:controlTypes.ROLE_EDITABLETEXT, wdContentControlText:controlTypes.ROLE_EDITABLETEXT, wdContentControlPicture:controlTypes.ROLE_GRAPHIC, wdContentControlComboBox:controlTypes.ROLE_COMBOBOX, wdContentControlDropdownList:controlTypes.ROLE_COMBOBOX, wdContentControlDate:controlTypes.ROLE_EDITABLETEXT, wdContentControlGroup:controlTypes.ROLE_GROUPING, wdContentControlCheckBox:controlTypes.ROLE_CHECKBOX, } winwordWindowIid=GUID('{00020962-0000-0000-C000-000000000046}') wm_winword_expandToLine=ctypes.windll.user32.RegisterWindowMessageW(u"wm_winword_expandToLine") NVDAUnitsToWordUnits={ textInfos.UNIT_CHARACTER:wdCharacter, textInfos.UNIT_WORD:wdWord, textInfos.UNIT_LINE:wdLine, textInfos.UNIT_SENTENCE:wdSentence, textInfos.UNIT_PARAGRAPH:wdParagraph, textInfos.UNIT_TABLE:wdTable, textInfos.UNIT_CELL:wdCell, textInfos.UNIT_ROW:wdRow, textInfos.UNIT_COLUMN:wdColumn, textInfos.UNIT_STORY:wdStory, textInfos.UNIT_READINGCHUNK:wdSentence, } formatConfigFlagsMap={ "reportFontName":0x1, "reportFontSize":0x2, "reportFontAttributes":0x4, "reportColor":0x8, "reportAlignment":0x10, "reportStyle":0x20, "reportSpellingErrors":0x40, "reportPage":0x80, "reportLineNumber":0x100, "reportTables":0x200, "reportLists":0x400, "reportLinks":0x800, "reportComments":0x1000, "reportHeadings":0x2000, "autoLanguageSwitching":0x4000, "reportRevisions":0x8000, "reportParagraphIndentation":0x10000, "reportLineSpacing":0x40000, } formatConfigFlag_includeLayoutTables=0x20000 class WordDocumentHeadingQuickNavItem(browseMode.TextInfoQuickNavItem): def __init__(self,nodeType,document,textInfo,level): self.level=level super(WordDocumentHeadingQuickNavItem,self).__init__(nodeType,document,textInfo) def isChild(self,parent): if not isinstance(parent,WordDocumentHeadingQuickNavItem): return False return self.level>parent.level class WordDocumentCollectionQuickNavItem(browseMode.TextInfoQuickNavItem): """ A QuickNavItem representing an item that MS Word stores as a collection (e.g. link, table etc). """ def rangeFromCollectionItem(self,item): """ Fetches a Microsoft Word range object from a Microsoft Word item in a collection. E.g. a HyperLink object. @param item: an item from a collection (E.g. a HyperLink object). """ return item.range def __init__(self,itemType,document,collectionItem): """ See L{TextInfoQuickNavItem} for itemType and document argument definitions. @param collectionItem: an item from an MS Word collection e.g. HyperLink object. """ self.collectionItem=collectionItem self.rangeObj=self.rangeFromCollectionItem(collectionItem) textInfo=BrowseModeWordDocumentTextInfo(document,None,_rangeObj=self.rangeObj) super(WordDocumentCollectionQuickNavItem,self).__init__(itemType,document,textInfo) class WordDocumentCommentQuickNavItem(WordDocumentCollectionQuickNavItem): @property def label(self): author=self.collectionItem.author date=self.collectionItem.date text=self.collectionItem.range.text return _(u"comment: {text} by {author} on {date}").format(author=author,text=text,date=date) def rangeFromCollectionItem(self,item): return item.scope class WordDocumentFieldQuickNavItem(WordDocumentCollectionQuickNavItem): def rangeFromCollectionItem(self,item): return item.result class WordDocumentRevisionQuickNavItem(WordDocumentCollectionQuickNavItem): @property def label(self): revisionType=wdRevisionTypeLabels.get(self.collectionItem.type) author=self.collectionItem.author or "" date=self.collectionItem.date description=self.collectionItem.formatDescription or "" text=(self.collectionItem.range.text or "")[:100] return _(u"{revisionType} {description}: {text} by {author} on {date}").format(revisionType=revisionType,author=author,text=text,date=date,description=description) class WinWordCollectionQuicknavIterator(object): """ Allows iterating over an MS Word collection (e.g. HyperLinks) emitting L{QuickNavItem} objects. """ quickNavItemClass=WordDocumentCollectionQuickNavItem #: the QuickNavItem class that should be instanciated and emitted. def __init__(self,itemType,document,direction,rangeObj,includeCurrent): """ See L{QuickNavItemIterator} for itemType, document and direction definitions. @param rangeObj: a Microsoft Word range object where the collection should be fetched from. @ param includeCurrent: if true then any item at the initial position will be also emitted rather than just further ones. """ self.document=document self.itemType=itemType self.direction=direction if direction else "next" self.rangeObj=rangeObj self.includeCurrent=includeCurrent def collectionFromRange(self,rangeObj): """ Fetches a Microsoft Word collection object from a Microsoft Word range object. E.g. HyperLinks from a range. @param rangeObj: a Microsoft Word range object. @return: a Microsoft Word collection object. """ raise NotImplementedError def filter(self,item): """ Only allows certain items fom a collection to be emitted. E.g. a table who's borders are enabled. @param item: an item from a Microsoft Word collection (e.g. HyperLink object). @return True if this item should be allowd, false otherwise. @rtype: bool """ return True def iterate(self): """ returns a generator that emits L{QuickNavItem} objects for this collection. """ if self.direction=="next": self.rangeObj.moveEnd(wdStory,1) elif self.direction=="previous": self.rangeObj.collapse(wdCollapseStart) self.rangeObj.moveStart(wdStory,-1) items=self.collectionFromRange(self.rangeObj) itemCount=items.count isFirst=True for index in xrange(1,itemCount+1): if self.direction=="previous": index=itemCount-(index-1) collectionItem=items[index] try: item=self.quickNavItemClass(self.itemType,self.document,collectionItem) except COMError: message = ("Error iterating over item with " "type: {type}, iteration direction: {dir}, total item count: {count}, item at index: {index}" "\nThis could be caused by an issue with some element within or a corruption of the word document." ).format(type=self.itemType, dir=self.direction, count=itemCount, index=index) log.debugWarning(message ,exc_info=True) continue itemRange=item.rangeObj # Skip over the item we're already on. if not self.includeCurrent and isFirst and ((self.direction=="next" and itemRange.start<=self.rangeObj.start) or (self.direction=="previous" and itemRange.end>self.rangeObj.end)): continue if not self.filter(collectionItem): continue yield item isFirst=False class LinkWinWordCollectionQuicknavIterator(WinWordCollectionQuicknavIterator): quickNavItemClass=WordDocumentFieldQuickNavItem def collectionFromRange(self,rangeObj): return rangeObj.fields def filter(self, item): t = item.type if t == FIELD_TYPE_REF: fieldText = item.code.text.strip().split(' ') # ensure that the text has a \\h in it return any( fieldText[i] == '\\h' for i in xrange(2, len(fieldText)) ) return t == FIELD_TYPE_HYPERLINK class CommentWinWordCollectionQuicknavIterator(WinWordCollectionQuicknavIterator): quickNavItemClass=WordDocumentCommentQuickNavItem def collectionFromRange(self,rangeObj): return rangeObj.comments class RevisionWinWordCollectionQuicknavIterator(WinWordCollectionQuicknavIterator): quickNavItemClass=WordDocumentRevisionQuickNavItem def collectionFromRange(self,rangeObj): return rangeObj.revisions class GraphicWinWordCollectionQuicknavIterator(WinWordCollectionQuicknavIterator): def collectionFromRange(self,rangeObj): return rangeObj.inlineShapes def filter(self,item): return 20: links[1].follow() return tempRange.expand(wdParagraph) fields=tempRange.fields for field in (fields.item(i) for i in xrange(1, fields.count+1)): if field.type != FIELD_TYPE_REF: continue fResult = field.result fResult.moveStart(wdCharacter,-1) # move back one visible character (passed the hidden text eg the code for the reference). fResStart = fResult.start +1 # don't include the character before the hidden text. fResEnd = fResult.end rObjStart = self._rangeObj.start rObjEnd = self._rangeObj.end # check to see if the _rangeObj is inside the fResult range if not (fResStart <= rObjStart and fResEnd >= rObjEnd): continue # text will be something like ' REF _Ref457210120 \\h ' fieldText = field.code.text.strip().split(' ') # the \\h field indicates that the field is a link if not any( fieldText[i] == '\\h' for i in xrange(2, len(fieldText)) ): log.debugWarning("no \\h for field xref: %s" % field.code.text) continue bookmarkKey = fieldText[1] # we want the _Ref12345 part # get book mark start, we need to look at the whole document to find the bookmark. tempRange.Expand(wdStory) bMark = tempRange.bookmarks(bookmarkKey) self._rangeObj.setRange(bMark.start, bMark.start) self.updateCaret() tiCopy = self.copy() tiCopy.expand(textInfos.UNIT_LINE) speech.speakTextInfo(tiCopy,reason=controlTypes.REASON_FOCUS) braille.handler.handleCaretMove(self) return def _expandToLineAtCaret(self): lineStart=ctypes.c_int() lineEnd=ctypes.c_int() res=NVDAHelper.localLib.nvdaInProcUtils_winword_expandToLine(self.obj.appModule.helperLocalBindingHandle,self.obj.documentWindowHandle,self._rangeObj.start,ctypes.byref(lineStart),ctypes.byref(lineEnd)) if res!=0 or lineStart.value==lineEnd.value or lineStart.value==-1 or lineEnd.value==-1: log.debugWarning("winword_expandToLine failed") self._rangeObj.expand(wdParagraph) return self._rangeObj.setRange(lineStart.value,lineEnd.value) def __init__(self,obj,position,_rangeObj=None): super(WordDocumentTextInfo,self).__init__(obj,position) if _rangeObj: self._rangeObj=_rangeObj.Duplicate return if isinstance(position,textInfos.Point): try: self._rangeObj=self.obj.WinwordDocumentObject.activeWindow.RangeFromPoint(position.x,position.y) except COMError: raise NotImplementedError elif position==textInfos.POSITION_SELECTION: self._rangeObj=self.obj.WinwordSelectionObject.range elif position==textInfos.POSITION_CARET: self._rangeObj=self.obj.WinwordSelectionObject.range self._rangeObj.Collapse() elif position==textInfos.POSITION_ALL: self._rangeObj=self.obj.WinwordSelectionObject.range self._rangeObj.Expand(wdStory) elif position==textInfos.POSITION_FIRST: self._rangeObj=self.obj.WinwordSelectionObject.range self._rangeObj.SetRange(0,0) elif position==textInfos.POSITION_LAST: self._rangeObj=self.obj.WinwordSelectionObject.range self._rangeObj.endOf(wdStory) self._rangeObj.move(wdCharacter,-1) elif isinstance(position,textInfos.offsets.Offsets): self._rangeObj=self.obj.WinwordSelectionObject.range self._rangeObj.SetRange(position.startOffset,position.endOffset) elif isinstance(position,WordDocumentTextInfo): # copying from one textInfo to another self._rangeObj=position._rangeObj.duplicate else: raise NotImplementedError("position: %s"%position) def getTextWithFields(self,formatConfig=None): if self.isCollapsed: return [] if self.obj.ignoreFormatting: return [self.text] extraDetail=formatConfig.get('extraDetail',False) if formatConfig else False if not formatConfig: formatConfig=config.conf['documentFormatting'] formatConfig['autoLanguageSwitching']=config.conf['speech'].get('autoLanguageSwitching',False) startOffset=self._rangeObj.start endOffset=self._rangeObj.end text=BSTR() formatConfigFlags=sum(y for x,y in formatConfigFlagsMap.iteritems() if formatConfig.get(x,False)) if self.shouldIncludeLayoutTables: formatConfigFlags+=formatConfigFlag_includeLayoutTables if self.obj.ignoreEditorRevisions: formatConfigFlags&=~formatConfigFlagsMap['reportRevisions'] res=NVDAHelper.localLib.nvdaInProcUtils_winword_getTextInRange(self.obj.appModule.helperLocalBindingHandle,self.obj.documentWindowHandle,startOffset,endOffset,formatConfigFlags,ctypes.byref(text)) if res or not text: log.debugWarning("winword_getTextInRange failed with %d"%res) return [self.text] commandList=XMLFormatting.XMLTextParser().parse(text.value) for index,item in enumerate(commandList): if isinstance(item,textInfos.FieldCommand): field=item.field if isinstance(field,textInfos.ControlField): item.field=self._normalizeControlField(field) elif isinstance(field,textInfos.FormatField): item.field=self._normalizeFormatField(field,extraDetail=extraDetail) elif index>0 and isinstance(item,basestring) and item.isspace(): #2047: don't expose language for whitespace as its incorrect for east-asian languages lastItem=commandList[index-1] if isinstance(lastItem,textInfos.FieldCommand) and isinstance(lastItem.field,textInfos.FormatField): try: del lastItem.field['language'] except KeyError: pass return commandList def _normalizeControlField(self,field): role=field.pop('role',None) if role=="heading": role=controlTypes.ROLE_HEADING elif role=="table": role=controlTypes.ROLE_TABLE field['table-rowcount']=int(field.get('table-rowcount',0)) field['table-columncount']=int(field.get('table-columncount',0)) elif role=="tableCell": role=controlTypes.ROLE_TABLECELL field['table-rownumber']=int(field.get('table-rownumber',0)) field['table-columnnumber']=int(field.get('table-columnnumber',0)) elif role=="footnote": role=controlTypes.ROLE_FOOTNOTE elif role=="endnote": role=controlTypes.ROLE_ENDNOTE elif role=="graphic": role=controlTypes.ROLE_GRAPHIC elif role=="object": progid=field.get("progid") if progid and progid.startswith("Equation.DSMT"): # MathType. role=controlTypes.ROLE_MATH else: role=controlTypes.ROLE_EMBEDDEDOBJECT else: fieldType=int(field.pop('wdFieldType',-1)) if fieldType!=-1: role=wdFieldTypesToNVDARoles.get(fieldType,controlTypes.ROLE_UNKNOWN) if fieldType==wdFieldFormCheckBox and int(field.get('wdFieldResult','0'))>0: field['states']=set([controlTypes.STATE_CHECKED]) elif fieldType==wdFieldFormDropDown: field['value']=field.get('wdFieldResult',None) fieldStatusText=field.pop('wdFieldStatusText',None) if fieldStatusText: field['name']=fieldStatusText field['alwaysReportName']=True else: fieldType=int(field.get('wdContentControlType',-1)) if fieldType!=-1: role=wdContentControlTypesToNVDARoles.get(fieldType,controlTypes.ROLE_UNKNOWN) if role==controlTypes.ROLE_CHECKBOX: fieldChecked=bool(int(field.get('wdContentControlChecked','0'))) if fieldChecked: field['states']=set([controlTypes.STATE_CHECKED]) fieldTitle=field.get('wdContentControlTitle',None) if fieldTitle: field['name']=fieldTitle field['alwaysReportName']=True if role is not None: field['role']=role if role==controlTypes.ROLE_TABLE and field.get('longdescription'): field['states']=set([controlTypes.STATE_HASLONGDESC]) storyType=int(field.pop('wdStoryType',0)) if storyType: name=storyTypeLocalizedLabels.get(storyType,None) if name: field['name']=name field['alwaysReportName']=True field['role']=controlTypes.ROLE_FRAME # Hack support for lazy fetching of row and column header text values class ControlField(textInfos.ControlField): def get(d,name,default=None): if name=="table-rowheadertext": try: cell=self._rangeObj.cells[1] except IndexError: log.debugWarning("no cells for table row, possibly on end of cell mark") return super(ControlField,d).get(name,default) return self.obj.fetchAssociatedHeaderCellText(cell,False) elif name=="table-columnheadertext": try: cell=self._rangeObj.cells[1] except IndexError: log.debugWarning("no cells for table row, possibly on end of cell mark") return super(ControlField,d).get(name,default) return self.obj.fetchAssociatedHeaderCellText(cell,True) else: return super(ControlField,d).get(name,default) newField=ControlField() newField.update(field) return newField def _normalizeFormatField(self,field,extraDetail=False): _startOffset=int(field.pop('_startOffset')) _endOffset=int(field.pop('_endOffset')) lineSpacingRule=field.pop('wdLineSpacingRule',None) lineSpacingVal=field.pop('wdLineSpacing',None) if lineSpacingRule is not None: lineSpacingRule=int(lineSpacingRule) if lineSpacingRule==wdLineSpaceSingle: # Translators: single line spacing field['line-spacing']=pgettext('line spacing value',"single") elif lineSpacingRule==wdLineSpaceDouble: # Translators: double line spacing field['line-spacing']=pgettext('line spacing value',"double") elif lineSpacingRule==wdLineSpace1pt5: # Translators: line spacing of 1.5 lines field['line-spacing']=pgettext('line spacing value',"1.5 lines") elif lineSpacingRule==wdLineSpaceExactly: # Translators: exact (minimum) line spacing field['line-spacing']=pgettext('line spacing value',"exact") elif lineSpacingRule==wdLineSpaceAtLeast: # Translators: line spacing of at least x point field['line-spacing']=pgettext('line spacing value',"at least %.1f pt")%float(lineSpacingVal) elif lineSpacingRule==wdLineSpaceMultiple: # Translators: line spacing of x lines field['line-spacing']=pgettext('line spacing value',"%.1f lines")%(float(lineSpacingVal)/12.0) revisionType=int(field.pop('wdRevisionType',0)) if revisionType==wdRevisionInsert: field['revision-insertion']=True elif revisionType==wdRevisionDelete: field['revision-deletion']=True elif revisionType: revisionLabel=wdRevisionTypeLabels.get(revisionType,None) if revisionLabel: field['revision']=revisionLabel color=field.pop('color',None) if color is not None: field['color']=self.obj.winwordColorToNVDAColor(int(color)) try: languageId = int(field.pop('wdLanguageId',0)) if languageId: field['language']=self._getLanguageFromLcid(languageId) except: log.debugWarning("language error",exc_info=True) pass for x in ("first-line-indent","left-indent","right-indent","hanging-indent"): v=field.get(x) if not v: continue v=float(v) if abs(v)<0.001: v=None else: v=self.obj.getLocalizedMeasurementTextForPointSize(v) field[x]=v return field def _getLanguageFromLcid(self, lcid): """ gets a normalized locale from a lcid """ lang = locale.windows_locale[lcid] if lang: return languageHandler.normalizeLanguage(lang) def expand(self,unit): if unit==textInfos.UNIT_LINE: try: if self._rangeObj.tables.count>0 and self._rangeObj.cells.count==0: unit=textInfos.UNIT_CHARACTER except COMError: pass if unit==textInfos.UNIT_LINE: self._expandToLineAtCaret() elif unit==textInfos.UNIT_CHARACTER: self._rangeObj.moveEnd(wdCharacter,1) elif unit in NVDAUnitsToWordUnits: self._rangeObj.Expand(NVDAUnitsToWordUnits[unit]) else: raise NotImplementedError("unit: %s"%unit) def compareEndPoints(self,other,which): if which=="startToStart": diff=self._rangeObj.Start-other._rangeObj.Start elif which=="startToEnd": diff=self._rangeObj.Start-other._rangeObj.End elif which=="endToStart": diff=self._rangeObj.End-other._rangeObj.Start elif which=="endToEnd": diff=self._rangeObj.End-other._rangeObj.End else: raise ValueError("bad argument - which: %s"%which) if diff<0: diff=-1 elif diff>0: diff=1 return diff def setEndPoint(self,other,which): if which=="startToStart": self._rangeObj.Start=other._rangeObj.Start elif which=="startToEnd": self._rangeObj.Start=other._rangeObj.End elif which=="endToStart": self._rangeObj.End=other._rangeObj.Start elif which=="endToEnd": self._rangeObj.End=other._rangeObj.End else: raise ValueError("bad argument - which: %s"%which) def _get_isCollapsed(self): if self._rangeObj.Start==self._rangeObj.End: return True else: return False def collapse(self,end=False): if end: oldEndOffset=self._rangeObj.end self._rangeObj.collapse(wdCollapseEnd if end else wdCollapseStart) if end and self._rangeObj.end0 and endPoint!="end" and unit not in (wdCharacter,wdWord) and (_rangeObj.start+1)==self.obj.WinwordDocumentObject.characters.count: return 0 return res def move(self,unit,direction,endPoint=None): if unit!=textInfos.UNIT_LINE: return self._move(unit,direction,endPoint) if direction==0 or direction>1 or direction<-1: raise NotImplementedError("moving by line is only supported collapsed and with a count of 1 or -1") oldOffset=self._rangeObj.end if endPoint=="end" else self._rangeObj.start newOffset=ctypes.c_long() # Try moving by line making use of the selection temporarily res=NVDAHelper.localLib.nvdaInProcUtils_winword_moveByLine(self.obj.appModule.helperLocalBindingHandle,self.obj.documentWindowHandle,oldOffset,1 if direction<0 else 0,ctypes.byref(newOffset)) if res==0: res=direction newOffset=newOffset.value if direction<0 and not endPoint and newOffset==oldOffset: # Moving backwards by line seemed to not move. # Therefore fallback to moving back a character, expanding to line and collapsing to start instead. self.move(textInfos.UNIT_CHARACTER,-1) self.expand(unit) self.collapse() elif direction>0 and not endPoint and newOffset7 else 0 isFirst=True while True: if not isFirst or includeCurrent: level=rangeObj.paragraphs[1].outlineLevel if level and 0=rangeObj.start: break rangeObj=newRangeObj def _iterNodesByType(self,nodeType,direction="next",pos=None): if pos: rangeObj=pos.innerTextInfo._rangeObj else: rangeObj=self.rootNVDAObject.WinwordDocumentObject.range(0,0) includeCurrent=False if pos else True if nodeType=="link": return LinkWinWordCollectionQuicknavIterator(nodeType,self,direction,rangeObj,includeCurrent).iterate() elif nodeType=="annotation": comments=CommentWinWordCollectionQuicknavIterator(nodeType,self,direction,rangeObj,includeCurrent).iterate() revisions=RevisionWinWordCollectionQuicknavIterator(nodeType,self,direction,rangeObj,includeCurrent).iterate() return browseMode.mergeQuickNavItemIterators([comments,revisions],direction) elif nodeType in ("table","container"): return TableWinWordCollectionQuicknavIterator(nodeType,self,direction,rangeObj,includeCurrent).iterate() elif nodeType=="graphic": return GraphicWinWordCollectionQuicknavIterator(nodeType,self,direction,rangeObj,includeCurrent).iterate() elif nodeType.startswith('heading'): return self._iterHeadings(nodeType,direction,rangeObj,includeCurrent) else: raise NotImplementedError def _activatePosition(self, info=None): if not info: info=self.makeTextInfo(textInfos.POSITION_CARET) info.activate() def script_nextRow(self,gesture): self.rootNVDAObject._moveInTable(row=True,forward=True) braille.handler.handleCaretMove(self) def script_previousRow(self,gesture): self.rootNVDAObject._moveInTable(row=True,forward=False) braille.handler.handleCaretMove(self) def script_nextColumn(self,gesture): self.rootNVDAObject._moveInTable(row=False,forward=True) braille.handler.handleCaretMove(self) def script_previousColumn(self,gesture): self.rootNVDAObject._moveInTable(row=False,forward=False) braille.handler.handleCaretMove(self) __gestures={ "kb:tab":"trapNonCommandGesture", "kb:shift+tab":"trapNonCommandGesture", "kb:control+alt+upArrow": "previousRow", "kb:control+alt+downArrow": "nextRow", "kb:control+alt+leftArrow": "previousColumn", "kb:control+alt+rightArrow": "nextColumn", # We want to fall back to MS Word's real page up and page down, rather than browseMode's faked 25 lines "kb:pageUp":None, "kb:pageDown":None, "kb:shift+pageUp":None, "kb:shift+pageDown":None, } class WordDocument(EditableTextWithoutAutoSelectDetection, Window): treeInterceptorClass=WordDocumentTreeInterceptor shouldCreateTreeInterceptor=False TextInfo=WordDocumentTextInfo def winwordColorToNVDAColor(self,val): if val>=0: # normal RGB value return colors.RGB.fromCOLORREF(val).name elif (val&0xffffffff)==0xff000000: # Translators: the default (automatic) color in Microsoft Word return _("default color") elif ((val>>28)&0xf)==0xd and ((val>>16)&0xff)==0x00: # An MS word color index Plus intencity # Made up of MS Word Theme Color index, hsv value ratio (MS Word darker percentage) and hsv saturation ratio (MS Word lighter percentage) # Info: http://www.wordarticles.com/Articles/Colours/2007.php#UIConsiderations saturationRatio=(val&0xff)/255.0 valueRatio=((val>>8)&0xff)/255.0 themeColorIndex=(val>>24)&0x0f # Convert the MS Word theme color index to an MS Office color scheme index schemeColorIndex=WdThemeColorIndexToMsoThemeColorSchemeIndex[themeColorIndex] # Lookup the rgb value for the MS Office scheme color index based on the current theme colorref=self.WinwordDocumentObject.documentTheme.themeColorScheme(schemeColorIndex).rgb # Convert the rgb value to hsv and apply the saturation and value ratios rgb=tuple(x/255.0 for x in colors.RGB.fromCOLORREF(colorref)) hsv=colorsys.rgb_to_hsv(*rgb) hsv=(hsv[0],hsv[1]*saturationRatio,hsv[2]*valueRatio) rgb=colorsys.hsv_to_rgb(*hsv) name=colors.RGB(rgb[0]*255,rgb[1]*255,rgb[2]*255).name return name else: raise ValueError("Unknown color format %x %x %x %x"%((val>>24)&0xff,(val>>16)&0xff,(val>>8)&0xff,val&0xff)) def _get_ignoreEditorRevisions(self): try: ignore=not self.WinwordWindowObject.view.showRevisionsAndComments except COMError: log.debugWarning("showRevisionsAndComments",exc_info=True) ignore=False self.ignoreEditorRevisions=ignore return ignore #: True if formatting should be ignored (text only) such as for spellCheck error field ignoreFormatting=False def __init__(self,*args,**kwargs): super(WordDocument,self).__init__(*args,**kwargs) def event_caret(self): curSelectionPos=self.makeTextInfo(textInfos.POSITION_SELECTION) lastSelectionPos=getattr(self,'_lastSelectionPos',None) self._lastSelectionPos=curSelectionPos if lastSelectionPos: if curSelectionPos._rangeObj.isEqual(lastSelectionPos._rangeObj): return super(WordDocument,self).event_caret() def _get_role(self): return controlTypes.ROLE_EDITABLETEXT def _get_states(self): states=super(WordDocument,self).states states.add(controlTypes.STATE_MULTILINE) return states def populateHeaderCellTrackerFromHeaderRows(self,headerCellTracker,table): rows=table.rows numHeaderRows=0 for rowIndex in xrange(rows.count): try: row=rows.item(rowIndex+1) except COMError: break try: headingFormat=row.headingFormat except (COMError,AttributeError,NameError): headingFormat=0 if headingFormat==-1: # is a header row numHeaderRows+=1 else: break if numHeaderRows>0: headerCellTracker.addHeaderCellInfo(rowNumber=1,columnNumber=1,rowSpan=numHeaderRows,isColumnHeader=True,isRowHeader=False) def populateHeaderCellTrackerFromBookmarks(self,headerCellTracker,bookmarks): for x in bookmarks: name=x.name lowerName=name.lower() isColumnHeader=isRowHeader=False if lowerName.startswith('title'): isColumnHeader=isRowHeader=True elif lowerName.startswith('columntitle'): isColumnHeader=True elif lowerName.startswith('rowtitle'): isRowHeader=True else: continue try: headerCell=x.range.cells.item(1) except COMError: continue headerCellTracker.addHeaderCellInfo(rowNumber=headerCell.rowIndex,columnNumber=headerCell.columnIndex,name=name,isColumnHeader=isColumnHeader,isRowHeader=isRowHeader) _curHeaderCellTrackerTable=None _curHeaderCellTracker=None def getHeaderCellTrackerForTable(self,table): tableRange=table.range if not self._curHeaderCellTrackerTable or not tableRange.isEqual(self._curHeaderCellTrackerTable.range): self._curHeaderCellTracker=HeaderCellTracker() self.populateHeaderCellTrackerFromBookmarks(self._curHeaderCellTracker,tableRange.bookmarks) self.populateHeaderCellTrackerFromHeaderRows(self._curHeaderCellTracker,table) self._curHeaderCellTrackerTable=table return self._curHeaderCellTracker def setAsHeaderCell(self,cell,isColumnHeader=False,isRowHeader=False): rowNumber=cell.rowIndex columnNumber=cell.columnIndex headerCellTracker=self.getHeaderCellTrackerForTable(cell.range.tables[1]) oldInfo=headerCellTracker.getHeaderCellInfoAt(rowNumber,columnNumber) if oldInfo: if isColumnHeader and not oldInfo.isColumnHeader: oldInfo.isColumnHeader=True elif isRowHeader and not oldInfo.isRowHeader: oldInfo.isRowHeader=True else: return False isColumnHeader=oldInfo.isColumnHeader isRowHeader=oldInfo.isRowHeader if isColumnHeader and isRowHeader: name="Title_" elif isRowHeader: name="RowTitle_" elif isColumnHeader: name="ColumnTitle_" else: raise ValueError("One or both of isColumnHeader or isRowHeader must be True") name+=uuid.uuid4().hex if oldInfo: self.WinwordDocumentObject.bookmarks[oldInfo.name].delete() oldInfo.name=name else: headerCellTracker.addHeaderCellInfo(rowNumber=rowNumber,columnNumber=columnNumber,name=name,isColumnHeader=isColumnHeader,isRowHeader=isRowHeader) self.WinwordDocumentObject.bookmarks.add(name,cell.range) return True def forgetHeaderCell(self,cell,isColumnHeader=False,isRowHeader=False): rowNumber=cell.rowIndex columnNumber=cell.columnIndex if not isColumnHeader and not isRowHeader: return False headerCellTracker=self.getHeaderCellTrackerForTable(cell.range.tables[1]) info=headerCellTracker.getHeaderCellInfoAt(rowNumber,columnNumber) if not info or not hasattr(info,'name'): return False if isColumnHeader and info.isColumnHeader: info.isColumnHeader=False elif isRowHeader and info.isRowHeader: info.isRowHeader=False else: return False headerCellTracker.removeHeaderCellInfo(info) self.WinwordDocumentObject.bookmarks(info.name).delete() if info.isColumnHeader or info.isRowHeader: self.setAsHeaderCell(cell,isColumnHeader=info.isColumnHeader,isRowHeader=info.isRowHeader) return True def fetchAssociatedHeaderCellText(self,cell,columnHeader=False): table=cell.range.tables[1] rowNumber=cell.rowIndex columnNumber=cell.columnIndex headerCellTracker=self.getHeaderCellTrackerForTable(table) for info in headerCellTracker.iterPossibleHeaderCellInfosFor(rowNumber,columnNumber,columnHeader=columnHeader): textList=[] if columnHeader: for headerRowNumber in xrange(info.rowNumber,info.rowNumber+info.rowSpan): tempColumnNumber=columnNumber while tempColumnNumber>=1: try: headerCell=table.cell(headerRowNumber,tempColumnNumber) except COMError: tempColumnNumber-=1 continue break textList.append(headerCell.range.text) else: for headerColumnNumber in xrange(info.columnNumber,info.columnNumber+info.colSpan): tempRowNumber=rowNumber while tempRowNumber>=1: try: headerCell=table.cell(tempRowNumber,headerColumnNumber) except COMError: tempRowNumber-=1 continue break textList.append(headerCell.range.text) text=" ".join(textList) if text: return text def script_setColumnHeader(self,gesture): scriptCount=scriptHandler.getLastScriptRepeatCount() if not config.conf['documentFormatting']['reportTableHeaders']: # Translators: a message reported in the SetColumnHeader script for Microsoft Word. ui.message(_("Cannot set headers. Please enable reporting of table headers in Document Formatting Settings")) return try: cell=self.WinwordSelectionObject.cells[1] except COMError: # Translators: a message when trying to perform an action on a cell when not in one in Microsoft word ui.message(_("Not in a table cell")) return if scriptCount==0: if self.setAsHeaderCell(cell,isColumnHeader=True,isRowHeader=False): # Translators: a message reported in the SetColumnHeader script for Microsoft Word. ui.message(_("Set row {rowNumber} column {columnNumber} as start of column headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex)) else: # Translators: a message reported in the SetColumnHeader script for Microsoft Word. ui.message(_("Already set row {rowNumber} column {columnNumber} as start of column headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex)) elif scriptCount==1: if self.forgetHeaderCell(cell,isColumnHeader=True,isRowHeader=False): # Translators: a message reported in the SetColumnHeader script for Microsoft Word. ui.message(_("Removed row {rowNumber} column {columnNumber} from column headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex)) else: # Translators: a message reported in the SetColumnHeader script for Microsoft Word. ui.message(_("Cannot find row {rowNumber} column {columnNumber} in column headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex)) script_setColumnHeader.__doc__=_("Pressing once will set this cell as the first column header for any cells lower and to the right of it within this table. Pressing twice will forget the current column header for this cell.") def script_setRowHeader(self,gesture): scriptCount=scriptHandler.getLastScriptRepeatCount() if not config.conf['documentFormatting']['reportTableHeaders']: # Translators: a message reported in the SetRowHeader script for Microsoft Word. ui.message(_("Cannot set headers. Please enable reporting of table headers in Document Formatting Settings")) return try: cell=self.WinwordSelectionObject.cells[1] except COMError: # Translators: a message when trying to perform an action on a cell when not in one in Microsoft word ui.message(_("Not in a table cell")) return if scriptCount==0: if self.setAsHeaderCell(cell,isColumnHeader=False,isRowHeader=True): # Translators: a message reported in the SetRowHeader script for Microsoft Word. ui.message(_("Set row {rowNumber} column {columnNumber} as start of row headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex)) else: # Translators: a message reported in the SetRowHeader script for Microsoft Word. ui.message(_("Already set row {rowNumber} column {columnNumber} as start of row headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex)) elif scriptCount==1: if self.forgetHeaderCell(cell,isColumnHeader=False,isRowHeader=True): # Translators: a message reported in the SetRowHeader script for Microsoft Word. ui.message(_("Removed row {rowNumber} column {columnNumber} from row headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex)) else: # Translators: a message reported in the SetRowHeader script for Microsoft Word. ui.message(_("Cannot find row {rowNumber} column {columnNumber} in row headers").format(rowNumber=cell.rowIndex,columnNumber=cell.columnIndex)) script_setRowHeader.__doc__=_("Pressing once will set this cell as the first row header for any cells lower and to the right of it within this table. Pressing twice will forget the current row header for this cell.") def script_reportCurrentHeaders(self,gesture): cell=self.WinwordSelectionObject.cells[1] rowText=self.fetchAssociatedHeaderCellText(cell,False) columnText=self.fetchAssociatedHeaderCellText(cell,True) ui.message("Row %s, column %s"%(rowText or "empty",columnText or "empty")) def _get_WinwordVersion(self): if not hasattr(self,'_WinwordVersion'): self._WinwordVersion=float(self.WinwordApplicationObject.version) return self._WinwordVersion def _get_documentWindowHandle(self): return self.windowHandle def _get_WinwordWindowObject(self): if not getattr(self,'_WinwordWindowObject',None): try: pDispatch=oleacc.AccessibleObjectFromWindow(self.documentWindowHandle,winUser.OBJID_NATIVEOM,interface=comtypes.automation.IDispatch) except (COMError, WindowsError): log.debugWarning("Could not get MS Word object model from window %s with class %s"%(self.documentWindowHandle,winUser.getClassName(self.documentWindowHandle)),exc_info=True) return None self._WinwordWindowObject=comtypes.client.dynamic.Dispatch(pDispatch) return self._WinwordWindowObject def _get_WinwordDocumentObject(self): if not getattr(self,'_WinwordDocumentObject',None): windowObject=self.WinwordWindowObject if not windowObject: return None self._WinwordDocumentObject=windowObject.document return self._WinwordDocumentObject def _get_WinwordApplicationObject(self): if not getattr(self,'_WinwordApplicationObject',None): self._WinwordApplicationObject=self.WinwordWindowObject.application return self._WinwordApplicationObject def _get_WinwordSelectionObject(self): if not getattr(self,'_WinwordSelectionObject',None): windowObject=self.WinwordWindowObject if not windowObject: return None self._WinwordSelectionObject=windowObject.selection return self._WinwordSelectionObject def _WaitForValueChangeForAction(self,action,fetcher,timeout=0.15): oldVal=fetcher() action() startTime=curTime=time.time() curVal=fetcher() while curVal==oldVal and (curTime-startTime)0 isCollapsed=info.isCollapsed if inTable: info.expand(textInfos.UNIT_CELL) speech.speakTextInfo(info,reason=controlTypes.REASON_FOCUS) braille.handler.handleCaretMove(self) def script_tab(self,gesture): gesture.send() info=self.makeTextInfo(textInfos.POSITION_SELECTION) inTable=info._rangeObj.tables.count>0 isCollapsed=info.isCollapsed if inTable and isCollapsed: info.expand(textInfos.UNIT_CELL) isCollapsed=False if not isCollapsed: speech.speakTextInfo(info,reason=controlTypes.REASON_FOCUS) braille.handler.handleCaretMove(self) if isCollapsed: offset=info._rangeObj.information(wdHorizontalPositionRelativeToPage) msg=self.getLocalizedMeasurementTextForPointSize(offset) ui.message(msg) if info._rangeObj.paragraphs[1].range.start==info._rangeObj.start: info.expand(textInfos.UNIT_LINE) speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) def getLocalizedMeasurementTextForPointSize(self,offset): options=self.WinwordApplicationObject.options useCharacterUnit=options.useCharacterUnit if useCharacterUnit: offset=offset/self.WinwordSelectionObject.font.size # Translators: a measurement in Microsoft Word return _("{offset:.3g} characters").format(offset=offset) else: unit=options.measurementUnit if unit==wdInches: offset=offset/72.0 # Translators: a measurement in Microsoft Word return _("{offset:.3g} inches").format(offset=offset) elif unit==wdCentimeters: offset=offset/28.35 # Translators: a measurement in Microsoft Word return _("{offset:.3g} centimeters").format(offset=offset) elif unit==wdMillimeters: offset=offset/2.835 # Translators: a measurement in Microsoft Word return _("{offset:.3g} millimeters").format(offset=offset) elif unit==wdPoints: # Translators: a measurement in Microsoft Word return _("{offset:.3g} points").format(offset=offset) elif unit==wdPicas: offset=offset/12.0 # Translators: a measurement in Microsoft Word # See http://support.microsoft.com/kb/76388 for details. return _("{offset:.3g} picas").format(offset=offset) def script_reportCurrentComment(self,gesture): info=self.makeTextInfo(textInfos.POSITION_CARET) info.expand(textInfos.UNIT_CHARACTER) fields=info.getTextWithFields(formatConfig={'reportComments':True}) for field in reversed(fields): if isinstance(field,textInfos.FieldCommand) and isinstance(field.field,textInfos.FormatField): commentReference=field.field.get('comment') if commentReference: offset=int(commentReference) range=self.WinwordDocumentObject.range(offset,offset+1) try: text=range.comments[1].range.text except COMError: break if text: ui.message(text) return # Translators: a message when there is no comment to report in Microsoft Word ui.message(_("No comments")) # Translators: a description for a script script_reportCurrentComment.__doc__=_("Reports the text of the comment where the System caret is located.") def script_changeLineSpacing(self,gesture): val=self._WaitForValueChangeForAction(lambda: gesture.send(),lambda:self.WinwordSelectionObject.ParagraphFormat.LineSpacingRule) if val == wdLineSpaceSingle: # Translators: a message when switching to single line spacing in Microsoft word ui.message(_("Single line spacing")) elif val == wdLineSpaceDouble: # Translators: a message when switching to double line spacing in Microsoft word ui.message(_("Double line spacing")) elif val == wdLineSpace1pt5: # Translators: a message when switching to 1.5 line spaceing in Microsoft word ui.message(_("1.5 line spacing")) def _moveInTable(self,row=True,forward=True): info=self.makeTextInfo(textInfos.POSITION_CARET) info.expand(textInfos.UNIT_CHARACTER) formatConfig=config.conf['documentFormatting'].copy() formatConfig['reportTables']=True commandList=info.getTextWithFields(formatConfig) if len(commandList)<3 or commandList[1].field.get('role',None)!=controlTypes.ROLE_TABLE or commandList[2].field.get('role',None)!=controlTypes.ROLE_TABLECELL: # Translators: The message reported when a user attempts to use a table movement command # when the cursor is not withnin a table. ui.message(_("Not in table")) return False rowCount=commandList[1].field.get('table-rowcount',1) columnCount=commandList[1].field.get('table-columncount',1) rowNumber=commandList[2].field.get('table-rownumber',1) columnNumber=commandList[2].field.get('table-columnnumber',1) try: table=info._rangeObj.tables[1] except COMError: log.debugWarning("Could not get MS Word table object indicated in XML") ui.message(_("Not in table")) return False _cell=table.cell getCell=lambda thisIndex,otherIndex: _cell(thisIndex,otherIndex) if row else _cell(otherIndex,thisIndex) thisIndex=rowNumber if row else columnNumber otherIndex=columnNumber if row else rowNumber thisLimit=(rowCount if row else columnCount) if forward else 1 limitOp=operator.le if forward else operator.ge incdecFunc=operator.add if forward else operator.sub foundCell=None curOtherIndex=otherIndex while curOtherIndex>0: curThisIndex=incdecFunc(thisIndex,1) while limitOp(curThisIndex,thisLimit): try: foundCell=getCell(curThisIndex,curOtherIndex).range except COMError: pass if foundCell: break curThisIndex=incdecFunc(curThisIndex,1) if foundCell: break curOtherIndex-=1 if not foundCell: ui.message(_("Edge of table")) return False newInfo=WordDocumentTextInfo(self,textInfos.POSITION_CARET,_rangeObj=foundCell) speech.speakTextInfo(newInfo,reason=controlTypes.REASON_CARET) newInfo.collapse() newInfo.updateCaret() return True def script_nextRow(self,gesture): self._moveInTable(row=True,forward=True) def script_previousRow(self,gesture): self._moveInTable(row=True,forward=False) def script_nextColumn(self,gesture): self._moveInTable(row=False,forward=True) def script_previousColumn(self,gesture): self._moveInTable(row=False,forward=False) def script_nextParagraph(self,gesture): info=self.makeTextInfo(textInfos.POSITION_CARET) # #4375: can't use self.move here as it may check document.chracters.count which can take for ever on large documents. info._rangeObj.move(wdParagraph,1) info.updateCaret() self._caretScriptPostMovedHelper(textInfos.UNIT_PARAGRAPH,gesture,None) script_nextParagraph.resumeSayAllMode=sayAllHandler.CURSOR_CARET def script_previousParagraph(self,gesture): info=self.makeTextInfo(textInfos.POSITION_CARET) # #4375: keeping cemetrical with nextParagraph script. info._rangeObj.move(wdParagraph,-1) info.updateCaret() self._caretScriptPostMovedHelper(textInfos.UNIT_PARAGRAPH,gesture,None) script_previousParagraph.resumeSayAllMode=sayAllHandler.CURSOR_CARET __gestures = { "kb:control+[":"increaseDecreaseFontSize", "kb:control+]":"increaseDecreaseFontSize", "kb:control+shift+,":"increaseDecreaseFontSize", "kb:control+shift+.":"increaseDecreaseFontSize", "kb:control+b":"toggleBold", "kb:control+i":"toggleItalic", "kb:control+u":"toggleUnderline", "kb:control+=":"toggleSuperscriptSubscript", "kb:control+shift+=":"toggleSuperscriptSubscript", "kb:control+l":"toggleAlignment", "kb:control+e":"toggleAlignment", "kb:control+r":"toggleAlignment", "kb:control+j":"toggleAlignment", "kb:alt+shift+downArrow":"moveParagraphDown", "kb:alt+shift+upArrow":"moveParagraphUp", "kb:alt+shift+rightArrow":"increaseDecreaseOutlineLevel", "kb:alt+shift+leftArrow":"increaseDecreaseOutlineLevel", "kb:control+shift+n":"increaseDecreaseOutlineLevel", "kb:control+alt+1":"increaseDecreaseOutlineLevel", "kb:control+alt+2":"increaseDecreaseOutlineLevel", "kb:control+alt+3":"increaseDecreaseOutlineLevel", "kb:control+1":"changeLineSpacing", "kb:control+2":"changeLineSpacing", "kb:control+5":"changeLineSpacing", "kb:tab": "tab", "kb:shift+tab": "tab", "kb:NVDA+shift+c":"setColumnHeader", "kb:NVDA+shift+r":"setRowHeader", "kb:NVDA+shift+h":"reportCurrentHeaders", "kb:control+alt+upArrow": "previousRow", "kb:control+alt+downArrow": "nextRow", "kb:control+alt+leftArrow": "previousColumn", "kb:control+alt+rightArrow": "nextColumn", "kb:control+downArrow":"nextParagraph", "kb:control+upArrow":"previousParagraph", "kb:alt+home":"caret_moveByCell", "kb:alt+end":"caret_moveByCell", "kb:alt+pageUp":"caret_moveByCell", "kb:alt+pageDown":"caret_moveByCell", "kb:alt+shift+home":"caret_changeSelection", "kb:alt+shift+end":"caret_changeSelection", "kb:alt+shift+pageUp":"caret_changeSelection", "kb:alt+shift+pageDown":"caret_changeSelection", "kb:control+pageUp": "caret_moveByLine", "kb:control+pageDown": "caret_moveByLine", "kb:NVDA+alt+c":"reportCurrentComment", } class WordDocument_WwN(WordDocument): def _get_documentWindowHandle(self): w=NVDAHelper.localLib.findWindowWithClassInThread(self.windowThreadID,u"_WwG",True) if not w: log.debugWarning("Could not find window for class _WwG in thread.") w=super(WordDocument_WwN,self).documentWindowHandle return w def _get_WinwordWindowObject(self): window=super(WordDocument_WwN,self).WinwordWindowObject if not window: return None try: return window.application.activeWindow.activePane except COMError: log.debugWarning("Unable to get activePane") return window.application.windows[1].activePane __gestures={ "kb:tab":None, "kb:shift+tab":None, } class ElementsListDialog(browseMode.ElementsListDialog): ELEMENT_TYPES=(browseMode.ElementsListDialog.ELEMENT_TYPES[0],browseMode.ElementsListDialog.ELEMENT_TYPES[1], # Translators: The label of a radio button to select the type of element # in the browse mode Elements List dialog. ("annotation", _("&Annotations")), )