#A part of NonVisual Desktop Access (NVDA) #Copyright (C) 2006-2008 NVDA Contributors #This file is covered by the GNU General Public License. #See the file COPYING for more details. import locale import winVersion import comtypes.client import struct import ctypes from comtypes import COMError import pythoncom import win32clipboard import oleTypes import colors import globalVars import eventHandler import comInterfaces.tom from logHandler import log import config import speech import winKernel import api import winUser import textInfos.offsets from keyboardHandler import KeyboardInputGesture from scriptHandler import isScriptWaiting import IAccessibleHandler import controlTypes from . import Window from .. import NVDAObjectTextInfo from ..behaviors import EditableTextWithAutoSelectDetection import braille import watchdog selOffsetsAtLastCaretEvent=None TA_BOTTOM=8 #Edit control window messages EM_GETSEL=176 EM_SETSEL=177 EM_SCROLLCARET=0xb7 EM_GETLINE=196 EM_GETLINECOUNT=186 EM_LINEFROMCHAR=201 EM_LINEINDEX=187 EM_LINELENGTH=193 EM_POSFROMCHAR=214 EM_CHARFROMPOS=215 EM_GETFIRSTVISIBLELINE=0x0ce #Rich edit messages EM_EXGETSEL=winUser.WM_USER+52 EM_EXLINEFROMCHAR=winUser.WM_USER+54 EM_EXSETSEL=winUser.WM_USER+55 EM_GETCHARFORMAT=winUser.WM_USER+58 EM_GETPARAFORMAT=winUser.WM_USER+61 EM_GETTEXTRANGE=winUser.WM_USER+75 EM_FINDWORDBREAK=winUser.WM_USER+76 #Rich edit 2.0 messages EM_GETTEXTEX=winUser.WM_USER+94 EM_GETTEXTLENGTHEX=winUser.WM_USER+95 #Rich edit 4.0 messages EM_GETPAGE=winUser.WM_USER+228 LF_FACESIZE=32 #structures class PointLStruct(ctypes.Structure): _fields_=[ ('x',ctypes.c_long), ('y',ctypes.c_long), ] class CharRangeStruct(ctypes.Structure): _fields_=[ ('cpMin',ctypes.c_long), ('cpMax',ctypes.c_long), ] class TextRangeUStruct(ctypes.Structure): _fields_=[ ('chrg',CharRangeStruct), ('lpstrText',ctypes.c_wchar_p), ] class TextRangeAStruct(ctypes.Structure): _fields_=[ ('chrg',CharRangeStruct), ('lpstrText',ctypes.c_char_p), ] CFM_LINK=0x20 CFE_AUTOBACKCOLOR=0x4000000 CFE_AUTOCOLOR=0x40000000 CFE_BOLD=1 CFE_ITALIC=2 CFE_UNDERLINE=4 CFE_STRIKEOUT=8 CFE_PROTECTED=16 CFE_SUBSCRIPT=0x00010000 # Superscript and subscript are CFE_SUPERSCRIPT=0x00020000 # mutually exclusive SCF_SELECTION=0x1 class CharFormat2WStruct(ctypes.Structure): _fields_=[ ('cbSize',ctypes.c_uint), ('dwMask',ctypes.wintypes.DWORD), ('dwEffects',ctypes.wintypes.DWORD), ('yHeight',ctypes.c_long), ('yOffset',ctypes.c_long), ('crTextColor',ctypes.wintypes.COLORREF), ('bCharSet',ctypes.c_byte), ('bPitchAndFamily',ctypes.c_byte), ('szFaceName',ctypes.c_wchar*LF_FACESIZE), ('wWeight',ctypes.wintypes.WORD), ('sSpacing',ctypes.c_short), ('crBackColor',ctypes.wintypes.COLORREF), ('lcid',ctypes.wintypes.LCID), ('dwReserved',ctypes.wintypes.DWORD), ('sStyle',ctypes.c_short), ('wKerning',ctypes.wintypes.WORD), ('bUnderlineType',ctypes.c_byte), ('bAnimation',ctypes.c_byte), ('bRevAuthor',ctypes.c_byte), ('bReserved1',ctypes.c_byte), ] class CharFormat2AStruct(ctypes.Structure): _fields_=CharFormat2WStruct._fields_[:] _fields_[8]=('szFaceName',ctypes.c_char*LF_FACESIZE) class getTextExStruct(ctypes.Structure): _fields_=[ ('cb',ctypes.wintypes.DWORD), ('flags',ctypes.wintypes.DWORD), ('codepage',ctypes.c_uint), ('lpDefaultChar',ctypes.wintypes.LPCSTR), ('lpUsedDefChar',ctypes.c_void_p), ] class getTextLengthExStruct(ctypes.Structure): _fields_=[ ('flags',ctypes.wintypes.DWORD), ('codepage',ctypes.c_uint), ] #getTextEx flags GT_DEFAULT=0 GT_USECRLF=1 GT_SELECTION=2 GT_RAWTEXT=4 GT_NOHIDDENTEXT=8 #getTextLengthEx flags GTL_DEFAULT=0 GTL_USECRLF=1 GTL_PRECISE=2 GTL_CLOSE=4 GTL_NUMCHARS=8 GTL_NUMBYTES=16 #findWordbreak constants WB_CLASSIFY=3 WB_MOVEWORDLEFT=4 WB_MOVEWORDRIGHT=5 WB_LEFTBREAK=6 WB_RIGHTBREAK=7 class EditTextInfo(textInfos.offsets.OffsetsTextInfo): def _getPointFromOffset(self,offset): if self.obj.editAPIVersion==1 or self.obj.editAPIVersion>=3: processHandle=self.obj.processHandle internalP=winKernel.virtualAllocEx(processHandle,None,ctypes.sizeof(PointLStruct),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) try: p=PointLStruct(0,0) winKernel.writeProcessMemory(processHandle,internalP,ctypes.byref(p),ctypes.sizeof(p),None) watchdog.cancellableSendMessage(self.obj.windowHandle,EM_POSFROMCHAR,internalP,offset) winKernel.readProcessMemory(processHandle,internalP,ctypes.byref(p),ctypes.sizeof(p),None) finally: winKernel.virtualFreeEx(processHandle,internalP,0,winKernel.MEM_RELEASE) point=textInfos.Point(p.x,p.y) else: res=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_POSFROMCHAR,offset,None) point=textInfos.Point(winUser.LOWORD(res),winUser.HIWORD(res)) (left,top,width,height)=self.obj.location point.x=point.x+left point.y=point.y+top return point def _getOffsetFromPoint(self,x,y): (left,top,width,height)=self.obj.location if self.obj.editAPIVersion>=1: processHandle=self.obj.processHandle internalP=winKernel.virtualAllocEx(processHandle,None,ctypes.sizeof(PointLStruct),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) try: p=PointLStruct(x-left,y-top) winKernel.writeProcessMemory(processHandle,internalP,ctypes.byref(p),ctypes.sizeof(p),None) offset=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_CHARFROMPOS,0,internalP) finally: winKernel.virtualFreeEx(processHandle,internalP,0,winKernel.MEM_RELEASE) else: p=(x-left)+((y-top)<<16) offset=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_CHARFROMPOS,0,p)&0xffff return offset def _getCharFormat(self,offset): oldSel=self._getSelectionOffsets() if oldSel!=(offset,offset+1): self._setSelectionOffsets(offset,offset+1) if self.obj.isWindowUnicode: charFormatStruct=CharFormat2WStruct else: charFormatStruct=CharFormat2AStruct charFormat=charFormatStruct() charFormat.cbSize=ctypes.sizeof(charFormatStruct) processHandle=self.obj.processHandle internalCharFormat=winKernel.virtualAllocEx(processHandle,None,ctypes.sizeof(charFormat),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) try: winKernel.writeProcessMemory(processHandle,internalCharFormat,ctypes.byref(charFormat),ctypes.sizeof(charFormat),None) watchdog.cancellableSendMessage(self.obj.windowHandle,EM_GETCHARFORMAT,SCF_SELECTION, internalCharFormat) winKernel.readProcessMemory(processHandle,internalCharFormat,ctypes.byref(charFormat),ctypes.sizeof(charFormat),None) finally: winKernel.virtualFreeEx(processHandle,internalCharFormat,0,winKernel.MEM_RELEASE) if oldSel!=(offset,offset+1): self._setSelectionOffsets(oldSel[0],oldSel[1]) return charFormat def _getFormatFieldAndOffsets(self,offset,formatConfig,calculateOffsets=True): #Basic edit fields do not support formatting at all if self.obj.editAPIVersion<1: return super(EditTextInfo,self)._getFormatFieldAndOffsets(offset,formatConfig,calculateOffsets=calculateOffsets) if calculateOffsets: startOffset,endOffset=self._getWordOffsets(offset) else: startOffset,endOffset=self._startOffset,self._endOffset formatField=textInfos.FormatField() charFormat=None if formatConfig["reportFontName"]: if charFormat is None: charFormat=self._getCharFormat(offset) formatField["font-name"]=charFormat.szFaceName if formatConfig["reportFontSize"]: if charFormat is None: charFormat=self._getCharFormat(offset) formatField["font-size"]="%spt"%(charFormat.yHeight/20) if formatConfig["reportFontAttributes"]: if charFormat is None: charFormat=self._getCharFormat(offset) formatField["bold"]=bool(charFormat.dwEffects&CFE_BOLD) formatField["italic"]=bool(charFormat.dwEffects&CFE_ITALIC) formatField["underline"]=bool(charFormat.dwEffects&CFE_UNDERLINE) formatField["strikethrough"]=bool(charFormat.dwEffects&CFE_STRIKEOUT) if charFormat.dwEffects&CFE_SUBSCRIPT: formatField["text-position"]="sub" elif charFormat.dwEffects&CFE_SUPERSCRIPT: formatField["text-position"]="super" if formatConfig["reportColor"]: if charFormat is None: charFormat=self._getCharFormat(offset) formatField["color"]=colors.RGB.fromCOLORREF(charFormat.crTextColor) if not charFormat.dwEffects&CFE_AUTOCOLOR else _("default color") formatField["background-color"]=colors.RGB.fromCOLORREF(charFormat.crBackColor) if not charFormat.dwEffects&CFE_AUTOBACKCOLOR else _("default color") if formatConfig["reportLineNumber"]: formatField["line-number"]=self._getLineNumFromOffset(offset)+1 if formatConfig["reportLinks"]: if charFormat is None: charFormat=self._getCharFormat(offset) formatField["link"]=bool(charFormat.dwEffects&CFM_LINK) return formatField,(startOffset,endOffset) def _getSelectionOffsets(self): if self.obj.editAPIVersion>=1: charRange=CharRangeStruct() processHandle=self.obj.processHandle internalCharRange=winKernel.virtualAllocEx(processHandle,None,ctypes.sizeof(charRange),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) try: watchdog.cancellableSendMessage(self.obj.windowHandle,EM_EXGETSEL,0, internalCharRange) winKernel.readProcessMemory(processHandle,internalCharRange,ctypes.byref(charRange),ctypes.sizeof(charRange),None) finally: winKernel.virtualFreeEx(processHandle,internalCharRange,0,winKernel.MEM_RELEASE) return (charRange.cpMin,charRange.cpMax) else: start=ctypes.c_uint() end=ctypes.c_uint() res=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_GETSEL,ctypes.byref(start),ctypes.byref(end)) return start.value,end.value def _setSelectionOffsets(self,start,end): if self.obj.editAPIVersion>=1: charRange=CharRangeStruct() charRange.cpMin=start charRange.cpMax=end processHandle=self.obj.processHandle internalCharRange=winKernel.virtualAllocEx(processHandle,None,ctypes.sizeof(charRange),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) try: winKernel.writeProcessMemory(processHandle,internalCharRange,ctypes.byref(charRange),ctypes.sizeof(charRange),None) watchdog.cancellableSendMessage(self.obj.windowHandle,EM_EXSETSEL,0, internalCharRange) finally: winKernel.virtualFreeEx(processHandle,internalCharRange,0,winKernel.MEM_RELEASE) else: watchdog.cancellableSendMessage(self.obj.windowHandle,EM_SETSEL,start,end) #Make sure the Window is always scrolled to the caret watchdog.cancellableSendMessage(self.obj.windowHandle,EM_SCROLLCARET,0,0) def _getCaretOffset(self): return self._getSelectionOffsets()[0] def _setCaretOffset(self,offset): return self._setSelectionOffsets(offset,offset) def _getStoryText(self): if controlTypes.STATE_PROTECTED in self.obj.states: return u'*'*(self._getStoryLength()-1) return self.obj.windowText def _getStoryLength(self): if self.obj.editAPIVersion>=2: info=getTextLengthExStruct() info.flags=GTL_NUMCHARS if self.obj.isWindowUnicode: info.codepage=1200 else: info.codepage=0 processHandle=self.obj.processHandle internalInfo=winKernel.virtualAllocEx(processHandle,None,ctypes.sizeof(info),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) try: winKernel.writeProcessMemory(processHandle,internalInfo,ctypes.byref(info),ctypes.sizeof(info),None) textLen=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_GETTEXTLENGTHEX,internalInfo,0) finally: winKernel.virtualFreeEx(processHandle,internalInfo,0,winKernel.MEM_RELEASE) return textLen+1 else: return watchdog.cancellableSendMessage(self.obj.windowHandle,winUser.WM_GETTEXTLENGTH,0,0)+1 def _getLineCount(self): return watchdog.cancellableSendMessage(self.obj.windowHandle,EM_GETLINECOUNT,0,0) def _getTextRange(self,start,end): if self.obj.editAPIVersion>=2: bufLen=((end-start)+1)*2 if self.obj.isWindowUnicode: textRange=TextRangeUStruct() else: textRange=TextRangeAStruct() textRange.chrg.cpMin=start textRange.chrg.cpMax=end processHandle=self.obj.processHandle internalBuf=winKernel.virtualAllocEx(processHandle,None,bufLen,winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) try: textRange.lpstrText=internalBuf internalTextRange=winKernel.virtualAllocEx(processHandle,None,ctypes.sizeof(textRange),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) try: winKernel.writeProcessMemory(processHandle,internalTextRange,ctypes.byref(textRange),ctypes.sizeof(textRange),None) res=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_GETTEXTRANGE,0,internalTextRange) finally: winKernel.virtualFreeEx(processHandle,internalTextRange,0,winKernel.MEM_RELEASE) buf=(ctypes.c_byte*bufLen)() winKernel.readProcessMemory(processHandle,internalBuf,buf,bufLen,None) finally: winKernel.virtualFreeEx(processHandle,internalBuf,0,winKernel.MEM_RELEASE) if self.obj.isWindowUnicode or (res>1 and (buf[res]!=0 or buf[res+1]!=0)): text=ctypes.cast(buf,ctypes.c_wchar_p).value else: text=unicode(ctypes.cast(buf,ctypes.c_char_p).value, errors="replace", encoding=locale.getlocale()[1]) # #4095: Some protected richEdit controls do not hide their password characters. # We do this specifically. # Note that protected standard edit controls get characters hidden in _getStoryText. if text and controlTypes.STATE_PROTECTED in self.obj.states: text=u'*'*len(text) else: text=self._getStoryText()[start:end] return text def _getWordOffsets(self,offset): if self.obj.editAPIVersion>=2: start=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_FINDWORDBREAK,WB_MOVEWORDLEFT,offset) end=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_FINDWORDBREAK,WB_MOVEWORDRIGHT,start) if end<=offset: start=end end=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_FINDWORDBREAK,WB_MOVEWORDRIGHT,offset) return (start,end) elif winVersion.winVersion.major<6: #Implementation of standard edit field wordbreak behaviour (only breaks on space) lineStart,lineEnd=self._getLineOffsets(offset) if offset>=lineEnd: return offset,offset+1 lineText=self._getTextRange(lineStart,lineEnd) lineTextLen=len(lineText) relativeOffset=offset-lineStart if relativeOffset>=lineTextLen: return offset,offset+1 #cariage returns are always treeted as a word by themselves if lineText[relativeOffset] in ['\r','\n']: return offset,offset+1 #Find the start of the word (possibly moving through space to get to the word first) tempOffset=relativeOffset while tempOffset>=0 and lineText[tempOffset].isspace(): tempOffset-=1 while tempOffset>=0 and not lineText[tempOffset].isspace(): tempOffset-=1 tempOffset+=1 start=lineStart+tempOffset startOnSpace=True if tempOffset=1: res=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_EXLINEFROMCHAR,0,offset) return res else: return watchdog.cancellableSendMessage(self.obj.windowHandle,EM_LINEFROMCHAR,offset,0) def _getLineOffsets(self,offset): lineNum=self._getLineNumFromOffset(offset) start=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_LINEINDEX,lineNum,0) length=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_LINELENGTH,offset,0) end=start+length #If we just seem to get invalid line info, calculate manually if start<=0 and end<=0 and lineNum<=0 and self._getLineCount()<=0 and self._getStoryLength()>0: return super(EditTextInfo,self)._getLineOffsets(offset) #Some edit controls that show both line feed and carage return can give a length not including the line feed if end<=offset: end=offset+1 #edit controls lye about their line length limit=self._getStoryLength() while self._getLineNumFromOffset(end)==lineNum and end0 if formatConfig["reportColor"]: if not fontObj: fontObj=range.font fgColor=fontObj.foreColor if fgColor==comInterfaces.tom.tomAutoColor: # Translators: The default color of text when a color has not been set by the author. formatField['color']=_("default color") elif fgColor&0xff000000: # The color is a palet index (we don't know the palet) # Translators: The color of text cannot be detected. formatField['color']=_("Unknown color") else: formatField["color"]=colors.RGB.fromCOLORREF(fgColor) bkColor=fontObj.backColor if bkColor==comInterfaces.tom.tomAutoColor: # Translators: The default background color when a color has not been set by the author. formatField['background-color']=_("default color") elif bkColor&0xff000000: # The color is a palet index (we don't know the palet) # Translators: The background color cannot be detected. formatField['background-color']=_("Unknown color") else: formatField["background-color"]=colors.RGB.fromCOLORREF(bkColor) return formatField def _expandFormatRange(self,range,formatConfig): startLimit=self._rangeObj.start endLimit=self._rangeObj.end chunkRange=range.duplicate if formatConfig["reportLineNumber"]: chunkRange.expand(comInterfaces.tom.tomLine) else: chunkRange.expand(comInterfaces.tom.tomParagraph) chunkStart=chunkRange.start chunkEnd=chunkRange.end if startLimitchunkEnd: endLimit=chunkEnd #range.moveEnd(comInterfaces.tom.tomCharFormat,1) range.expand(comInterfaces.tom.tomCharFormat) if range.end>endLimit: range.end=endLimit if range.start=2007 exposes MSAA on its embedded objects thus we can use accName as the label import oleacc try: label=o.QueryInterface(oleacc.IAccessible).accName(0); except comtypes.COMError: pass if label: return label # Outlook 2003 and Outlook Express write the embedded object text to the display with GDI thus we can use display model left,top=embedRangeObj.GetPoint(comInterfaces.tom.tomStart) right,bottom=embedRangeObj.GetPoint(comInterfaces.tom.tomEnd|TA_BOTTOM) # Outlook Express bug: when expanding to the first embedded object on lines after the first, the range's start coordinates are the start coordinates of the previous character (on the line above) # Therefore if we detect this, collapse the range and try getting left and top again if left>=right: r=embedRangeObj.duplicate r.collapse(1) left,top=r.GetPoint(comInterfaces.tom.tomStart) import displayModel label=displayModel.DisplayModelTextInfo(self.obj, textInfos.Rect(left, top, right, bottom)).text if label and not label.isspace(): return label # Windows Live Mail exposes the label via the embedded object's data (IDataObject) try: dataObj=o.QueryInterface(oleTypes.IDataObject) except comtypes.COMError: dataObj=None if dataObj: try: dataObj=pythoncom._univgw.interface(hash(dataObj),pythoncom.IID_IDataObject) format=(win32clipboard.CF_UNICODETEXT, None, pythoncom.DVASPECT_CONTENT, -1, pythoncom.TYMED_HGLOBAL) medium=dataObj.GetData(format) buf=ctypes.create_string_buffer(medium.data) buf=ctypes.cast(buf,ctypes.c_wchar_p) label=buf.value except: pass if label: return label # As a final fallback (e.g. could not get display model text for Outlook Express), use the embedded object's user type (e.g. "recipient"). try: oleObj=o.QueryInterface(oleTypes.IOleObject) label=oleObj.GetUserType(1) except comtypes.COMError: pass return label def _getTextAtRange(self,rangeObj): embedRangeObj=None bufText=rangeObj.text if not bufText: return u"" if controlTypes.STATE_PROTECTED in self.obj.states: return u'*'*len(bufText) newTextList=[] start=rangeObj.start for offset in xrange(len(bufText)): if ord(bufText[offset])==0xfffc: if embedRangeObj is None: embedRangeObj=rangeObj.duplicate embedRangeObj.setRange(start+offset,start+offset+1) label=self._getEmbeddedObjectLabel(embedRangeObj) if label: newTextList.append(label) else: newTextList.append(_("embedded object")) else: newTextList.append(bufText[offset]) return "".join(newTextList) def __init__(self,obj,position,_rangeObj=None): super(ITextDocumentTextInfo,self).__init__(obj,position) if _rangeObj: self._rangeObj=_rangeObj.Duplicate return if isinstance(position,textInfos.Point): self._rangeObj=self.obj.ITextDocumentObject.rangeFromPoint(position.x,position.y) elif position==textInfos.POSITION_ALL: self._rangeObj=self.obj.ITextDocumentObject.range(0,0) self._rangeObj.expand(comInterfaces.tom.tomStory) elif position==textInfos.POSITION_SELECTION: self._rangeObj=self.obj.ITextSelectionObject.duplicate elif position==textInfos.POSITION_CARET: self._rangeObj=self.obj.ITextSelectionObject.duplicate self._rangeObj.Collapse(True) elif position==textInfos.POSITION_FIRST: self._rangeObj=self.obj.ITextDocumentObject.range(0,0) elif position==textInfos.POSITION_LAST: self._rangeObj=self.obj.ITextDocumentObject.range(0,0) self._rangeObj.move(comInterfaces.tom.tomStory,1) self._rangeObj.moveStart(comInterfaces.tom.tomCharacter,-1) elif isinstance(position,textInfos.offsets.Offsets): self._rangeObj=self.obj.ITextDocumentObject.range(position.startOffset,position.endOffset) else: raise NotImplementedError("position: %s"%position) def getTextWithFields(self,formatConfig=None): if not formatConfig: formatConfig=config.conf["documentFormatting"] range=self._rangeObj.duplicate range.collapse(True) if not formatConfig["detectFormatAfterCursor"]: range.expand(comInterfaces.tom.tomCharacter) return [textInfos.FieldCommand("formatChange",self._getFormatFieldAtRange(range,formatConfig)), self._getTextAtRange(self._rangeObj)] commandList=[] endLimit=self._rangeObj.end while range.end0: 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): a=self._rangeObj.Start b=self._rangeObj.end startOffset=min(a,b) endOffset=max(a,b) if not end: offset=startOffset else: offset=endOffset self._rangeObj.SetRange(offset,offset) def copy(self): return ITextDocumentTextInfo(self.obj,None,_rangeObj=self._rangeObj) def _get_text(self): return self._getTextAtRange(self._rangeObj) def move(self,unit,direction,endPoint=None): if unit in NVDAUnitsToITextDocumentUnits: unit=NVDAUnitsToITextDocumentUnits[unit] else: raise NotImplementedError("unit: %s"%unit) if endPoint=="start": moveFunc=self._rangeObj.MoveStart elif endPoint=="end": moveFunc=self._rangeObj.MoveEnd else: moveFunc=self._rangeObj.Move res=moveFunc(unit,direction) return res def _get_bookmark(self): return textInfos.offsets.Offsets(self._rangeObj.start,self._rangeObj.end) def updateCaret(self): self.obj.ITextSelectionObject.start=self.obj.ITextSelectionObject.end=self._rangeObj.start def updateSelection(self): self.obj.ITextSelectionObject.start=self._rangeObj.start self.obj.ITextSelectionObject.end=self._rangeObj.end class Edit(EditableTextWithAutoSelectDetection, Window): editAPIVersion=0 editAPIUnicode=True # #4291: Use ITextDocument in Windows 7 and later, as it's very slow in earlier versions. useITextDocumentSupport=(winVersion.winVersion.major,winVersion.winVersion.minor)>=(6,1) editValueUnit=textInfos.UNIT_LINE def _get_TextInfo(self): if self.editAPIVersion>1 and (self.useITextDocumentSupport or self.windowClassName.endswith('PT')) and self.ITextDocumentObject: return ITextDocumentTextInfo else: return EditTextInfo def _get_ITextDocumentObject(self): if not hasattr(self,'_ITextDocumentObject'): try: ptr=ctypes.POINTER(comInterfaces.tom.ITextDocument)() ctypes.windll.oleacc.AccessibleObjectFromWindow(self.windowHandle,-16,ctypes.byref(ptr._iid_),ctypes.byref(ptr)) self._ITextDocumentObject=ptr except: log.error("Error getting ITextDocument",exc_info=True) self._ITextDocumentObject=None return self._ITextDocumentObject def _get_ITextSelectionObject(self): if not hasattr(self,'_ITextSelectionObject'): try: self._ITextSelectionObject=self.ITextDocumentObject.selection except: self._ITextSelectionObject=None return self._ITextSelectionObject def _get_value(self): return None def _get_role(self): return controlTypes.ROLE_EDITABLETEXT def event_caret(self): global selOffsetsAtLastCaretEvent #Fetching formatting and calculating word offsets needs to move the caret, so try to ignore these events selOffsets=self.makeTextInfo(textInfos.POSITION_SELECTION).bookmark if selOffsets==selOffsetsAtLastCaretEvent: return selOffsetsAtLastCaretEvent=selOffsets #Make sure that this object *really* has the focus before bothering to speak any possible selection change api.processPendingEvents() if self is not api.getFocusObject() or eventHandler.isPendingEvents('gainFocus'): return if eventHandler.isPendingEvents('valueChange',self): self.hasContentChangedSinceLastSelection=True super(Edit,self).event_caret() self.detectPossibleSelectionChange() def event_valueChange(self): self.event_textChange() def _get_states(self): states = super(Edit, self)._get_states() if self.windowStyle & winUser.ES_MULTILINE: states.add(controlTypes.STATE_MULTILINE) return states class RichEdit(Edit): editAPIVersion=1 def makeTextInfo(self,position): if self.TextInfo is not ITextDocumentTextInfo: return super(RichEdit,self).makeTextInfo(position) # #4090: Sometimes ITextDocument support can fail (security restrictions in Outlook 2010) # We then fall back to normal Edit support. try: return self.TextInfo(self,position) except COMError: log.debugWarning("Could not instanciate ITextDocumentTextInfo",exc_info=True) self.TextInfo=EditTextInfo return self.TextInfo(self,position) class RichEdit20(RichEdit): editAPIVersion=2 class RichEdit30(RichEdit): editAPIVersion=3 class RichEdit50(RichEdit): editAPIVersion=5