1 from ctypes import *
2 from ctypes.wintypes import RECT
3 from comtypes import BSTR
4 import math
5 import colors
6 import XMLFormatting
7 import api
8 import winUser
9 import NVDAHelper
10 import textInfos
11 from textInfos.offsets import OffsetsTextInfo
12 import watchdog
13
14 _getWindowTextInRect=None
15 _requestTextChangeNotificationsForWindow=None
16
17 _textChangeNotificationObjs=[]
18
20 global _getWindowTextInRect,_requestTextChangeNotificationsForWindow
21 _getWindowTextInRect=CFUNCTYPE(c_long,c_long,c_long,c_int,c_int,c_int,c_int,c_int,c_int,c_bool,POINTER(BSTR),POINTER(BSTR))(('displayModel_getWindowTextInRect',NVDAHelper.localLib),((1,),(1,),(1,),(1,),(1,),(1,),(1,),(1,),(1,),(2,),(2,)))
22 _requestTextChangeNotificationsForWindow=NVDAHelper.localLib.displayModel_requestTextChangeNotificationsForWindow
23
24 -def getWindowTextInRect(bindingHandle, windowHandle, left, top, right, bottom,minHorizontalWhitespace,minVerticalWhitespace,useXML=False):
25 text, cpBuf = watchdog.cancellableExecute(_getWindowTextInRect, bindingHandle, windowHandle, left, top, right, bottom,minHorizontalWhitespace,minVerticalWhitespace,useXML)
26 if not text or not cpBuf:
27 return u"",[]
28
29 characterRects = []
30 cpBufIt = iter(cpBuf)
31 for cp in cpBufIt:
32 characterRects.append((ord(cp), ord(next(cpBufIt)), ord(next(cpBufIt)), ord(next(cpBufIt))))
33 return text, characterRects
34
36 """Request or cancel notifications for when the display text changes in an NVDAObject.
37 A textChange event (event_textChange) will be fired on the object when its text changes.
38 Note that this event does not provide any information about the changed text itself.
39 It is important to request that notifications be cancelled when you no longer require them or when the object is no longer in use,
40 as otherwise, resources will not be released.
41 @param obj: The NVDAObject for which text change notifications are desired.
42 @type obj: NVDAObject
43 @param enable: C{True} to enable notifications, C{False} to disable them.
44 @type enable: bool
45 """
46 if not enable:
47 _textChangeNotificationObjs.remove(obj)
48 watchdog.cancellableExecute(_requestTextChangeNotificationsForWindow, obj.appModule.helperLocalBindingHandle, obj.windowHandle, enable)
49 if enable:
50 _textChangeNotificationObjs.append(obj)
51
52 -def textChangeNotify(windowHandle, left, top, right, bottom):
53 for obj in _textChangeNotificationObjs:
54 if windowHandle == obj.windowHandle:
55
56
57 obj.event_textChange()
58
59 -class DisplayModelTextInfo(OffsetsTextInfo):
60
61 minHorizontalWhitespace=8
62 minVerticalWhitespace=32
63
64 _cache__textAndRects = True
65 - def _get__textAndRects(self,useXML=False):
66 try:
67 left, top, width, height = self.obj.location
68 except TypeError:
69
70 return u"", []
71 return getWindowTextInRect(self.obj.appModule.helperLocalBindingHandle, self.obj.windowHandle, left, top, left + width, top + height,self.minHorizontalWhitespace,self.minVerticalWhitespace,useXML)
72
73 - def _getStoryText(self):
74 return self._textAndRects[0]
75
77 return len(self._getStoryText())
78
80 return self._getStoryText()[start:end]
81
82 - def getTextWithFields(self,formatConfig=None):
83 start=self._startOffset
84 end=self._endOffset
85 if start==end:
86 return u""
87 text=self._get__textAndRects(useXML=True)[0]
88 if not text:
89 return u""
90 text="<control>%s</control>"%text
91 commandList=XMLFormatting.XMLTextParser().parse(text)
92
93 stringOffset=0
94 for index in xrange(len(commandList)-1):
95 command=commandList[index]
96 if isinstance(command,basestring):
97 stringLen=len(command)
98 if (stringOffset+stringLen)<=start:
99 stringOffset+=stringLen
100 else:
101 del commandList[1:index-1]
102 commandList[2]=command[start-stringOffset:]
103 break
104 end=end-start
105 stringOffset=0
106 for index in xrange(1,len(commandList)-1):
107 command=commandList[index]
108 if isinstance(command,basestring):
109 stringLen=len(command)
110 if (stringOffset+stringLen)<end:
111 stringOffset+=stringLen
112 else:
113 commandList[index]=command[0:end-stringOffset]
114 del commandList[index+1:-1]
115 break
116 for item in commandList:
117 if isinstance(item,textInfos.FieldCommand) and isinstance(item.field,textInfos.FormatField):
118 self._normalizeFormatField(item.field)
119 return commandList
120
122 field['bold']=True if field.get('bold')=="true" else False
123 field['italic']=True if field.get('italic')=="true" else False
124 field['underline']=True if field.get('underline')=="true" else False
125 color=field.get('color')
126 if color is not None:
127 field['color']=colors.RGB.fromCOLORREF(int(color))
128 bkColor=field.get('background-color')
129 if bkColor is not None:
130 field['background-color']=colors.RGB.fromCOLORREF(int(bkColor))
131
132
133 - def _getPointFromOffset(self, offset):
134 text,rects=self._textAndRects
135 if not text or not rects or offset>=len(rects):
136 raise LookupError
137 x,y=rects[offset][:2]
138 return textInfos.Point(x, y)
139
140 - def _getOffsetFromPoint(self, x, y):
141 for charOffset, (charLeft, charTop, charRight, charBottom) in enumerate(self._textAndRects[1]):
142 if charLeft<=x<charRight and charTop<=y<charBottom:
143 return charOffset
144 raise LookupError
145
147
148 a=enumerate(self._textAndRects[1])
149
150 b=((charOffset,(charLeft+(charRight-charLeft)/2,charTop+(charBottom-charTop)/2)) for charOffset,(charLeft,charTop,charRight,charBottom) in a)
151
152
153 c=((math.sqrt(abs(x-cx)**2+abs(y-cy)**2),charOffset) for charOffset,(cx,cy) in b)
154
155 d=sorted(c)
156
157 return d[0][1] if len(d)>0 else 0
158
160 try:
161 p=self._getPointFromOffset(offset)
162 except (NotImplementedError,LookupError):
163 return self.obj
164 obj=api.getDesktopObject().objectFromPoint(p.x,p.y)
165 from NVDAObjects.window import Window
166 if not obj or not isinstance(obj,Window) or not winUser.isDescendantWindow(self.obj.windowHandle,obj.windowHandle):
167 return self.obj
168 return obj
169
171 l=obj.location
172 if not l:
173 raise RuntimeError
174 x=l[0]+(l[2]/2)
175 y=l[1]+(l[3]/2)
176 offset=self._getClosestOffsetFromPoint(x,y)
177 return offset,offset
178
180 return super(DisplayModelTextInfo,self).clipboardText.replace('\0',' ')
181
182 -class EditableTextDisplayModelTextInfo(DisplayModelTextInfo):
183
184 minHorizontalWhitespace=1
185 minVerticalWhitespace=4
186
187 - def _getCaretOffset(self):
188 caretRect = winUser.getGUIThreadInfo(self.obj.windowThreadID).rcCaret
189 objLocation=self.obj.location
190 objRect=RECT(objLocation[0],objLocation[1],objLocation[0]+objLocation[2],objLocation[1]+objLocation[3])
191 tempPoint = winUser.POINT()
192 tempPoint.x=caretRect.left
193 tempPoint.y=caretRect.top
194 winUser.user32.ClientToScreen(self.obj.windowHandle, byref(tempPoint))
195 caretRect.left=max(objRect.left,tempPoint.x)
196 caretRect.top=max(objRect.top,tempPoint.y)
197 tempPoint.x=caretRect.right
198 tempPoint.y=caretRect.bottom
199 winUser.user32.ClientToScreen(self.obj.windowHandle, byref(tempPoint))
200 caretRect.right=min(objRect.right,tempPoint.x)
201 caretRect.bottom=min(objRect.bottom,tempPoint.y)
202 import speech
203 for charOffset, (charLeft, charTop, charRight, charBottom) in enumerate(self._textAndRects[1]):
204
205 if caretRect.left>=charLeft and caretRect.right<=charRight and ((caretRect.top<=charTop and caretRect.bottom>=charBottom) or (caretRect.top>=charTop and caretRect.bottom<=charBottom)):
206 return charOffset
207 raise RuntimeError
208
209 - def _setCaretOffset(self,offset):
210 rects=self._textAndRects[1]
211 if offset>=len(rects):
212 raise RuntimeError("offset %d out of range")
213 left,top,right,bottom=rects[offset]
214 x=left
215 y=top+(bottom-top)/2
216 oldX,oldY=winUser.getCursorPos()
217 winUser.setCursorPos(x,y)
218 winUser.mouse_event(winUser.MOUSEEVENTF_LEFTDOWN,0,0,None,None)
219 winUser.mouse_event(winUser.MOUSEEVENTF_LEFTUP,0,0,None,None)
220 winUser.setCursorPos(oldX,oldY)
221
223 offset=self._getCaretOffset()
224 return offset,offset
225
226 - def _setSelectionOffsets(self,start,end):
227 if start!=end:
228 raise TypeError("Expanded selections not supported")
229 self._setCaretOffset(start)
230