1
2
3
4 avg = None
5 g_Player = None
6 g_FocusContext = None
7 g_LastKeyEvent = None
8 g_activityCallback = None
9 g_LastKeyRepeated = 0
10 g_RepeatDelay = 0.2
11 g_CharDelay = 0.1
12
13 import time
14
15 KEYCODE_TAB = 9
16 KEYCODE_SHTAB = 25
17 KEYCODE_FORMFEED = 12
18 KEYCODES_BACKSPACE = (8,127)
19
20 DEFAULT_CURSOR_PX = 'tacursorpx.png'
21 CURSOR_PADDING_PCT = 15
22 CURSOR_WIDTH_PCT = 4
23 CURSOR_SPACING_PCT = 4
24 CURSOR_FLASHING_DELAY = 1000
25 CURSOR_FLASH_AFTER_INACTIVITY = 200
26
27 import os.path
28 import time
29 try:
30 from . import avg
31 except ValueError:
32 pass
33
35 """
36 This object serves as a grouping element for TextAreas.
37 TextArea elements that belong to the same FocusContext cycle
38 focus among themselves. There can be several FocusContextes but
39 only one at once can be activated ( using the global function
40 setActiveFocusContext() )
41 """
43 self.__elements = []
44 self.__isActive = False
45
47 """
48 Test if this FocusContext is active
49 """
50 return self.__isActive
51
52 - def register(self, taElement):
53 """
54 Register a floating textarea with this FocusContext
55 @param taElement: TextArea, a reference to a TextArea
56 """
57 self.__elements.append(taElement)
58
59 - def getFocused(self):
60 """
61 Returns a TextArea element that currently has focus within
62 this FocusContext
63 """
64 for ob in self.__elements:
65 if ob.hasFocus():
66 return ob
67 return None
68
69 - def keyCharPressed(self, kchar):
70 """
71 Use this method to inject a character to active
72 (w/ focus) TextArea, convenience method for keyUCodePressed()
73 @param kchar: string, a single character
74 """
75 uch = unicode(kchar, 'utf-8')
76 self.keyUCodePressed(ord(uch[0]))
77
78 - def keyUCodePressed(self, keycode):
79 """
80 Shift a character (Unicode) into the active (w/focus)
81 TextArea
82 @param keycode: int, unicode code point of the character
83 """
84
85 if keycode == KEYCODE_TAB:
86 self.cycleFocus()
87 return
88
89 if keycode == KEYCODE_SHTAB:
90 self.cycleFocus(True)
91 return
92
93 for ob in self.__elements:
94 if ob.hasFocus():
95 ob.onKeyDown(keycode)
96
97 - def backspace(self):
98 """
99 Emulates a backspace character
100 """
101 self.keyUCodePressed(KEYCODES_BACKSPACE[0])
102
104 """
105 Clears the active textarea, emulating the press of FF character
106 """
107 self.keyUCodePressed(KEYCODE_FORMFEED)
108
109 - def resetFocuses(self):
110 """
111 Blurs every TextArea registered within this FocusContext
112 """
113 for ob in self.__elements:
114 ob.clearFocus()
115
116 - def cycleFocus(self, backwards=False):
117 """
118 Force a focus cycle among instantiated textareas
119 """
120
121 els = []
122 els.extend(self.__elements)
123
124 if len(els) == 0:
125 return
126
127 if backwards:
128 els.reverse()
129
130 elected = 0
131 for ob in els:
132 if not ob.hasFocus():
133 elected = elected + 1
134 else:
135 break
136
137
138
139 if elected in (len(els), len(els)-1):
140 elected = 0
141 else:
142 elected = elected + 1
143
144 for ob in els:
145 ob.setFocus(False)
146
147 els[elected].setFocus(True)
148
149 - def getRegistered(self):
150 """
151 Returns a list of TextArea currently registered within this FocusContext
152 """
153 return self.__elements
154
155 - def _switchActive(self, active):
156 """
157 De/Activates this FocusContext. Active FocusContexts route to the focused
158 textarea the keypress stream
159 Use textarea.setActiveFocusContext() instead using it directly
160 @param active: boolean, set to True to activate, False to deactivate
161 """
162 if active:
163 self.resetFocuses()
164 self.cycleFocus()
165 else:
166 self.resetFocuses()
167
168 self.__isActive = active
169
170
172 """
173 TextArea is an extended <words> node that reacts to user input
174 (mouse/touch for focus, keyboard for text input).
175 It sits in a given container matching its dimensions
176 """
177 - def __init__(self, parent, focusContext=None, bgImageFile=None, disableMouseFocus=False,
178 blurOpacity=0.3, border=0, id='', cursorPixFile=None):
179 """
180 @param parent: a div node with defined dimensions
181 @param focusContext: FocusContext object which groups focus for TextArea elements
182 @param bgImageFile: path and file name (relative to mediadir) of an image
183 that is used as a background for TextArea. The image is stretched to extents
184 of the instance
185 @param disableMouseFocus: boolean, prevents that mouse can set focus for
186 this instance
187 @param blurOpacity: opacity that textarea gets when goes to blur state
188 @param border: amount of offsetting pixels that words node will have from image extents
189 @param id: optional handle to identify the object when dealing with events. ID uniqueness
190 is not guaranteed
191 @param cursorPixFile: one-pixel graphic file used to render the cursor. The default one is
192 a black one
193 """
194 global g_Player
195 g_Player = avg.Player.get()
196 self.__parent = parent
197 self.__focusContext = focusContext
198 self.__blurOpacity = blurOpacity
199 self.__border = border
200 self.__id = id
201
202 if bgImageFile is not None:
203 bgNode = g_Player.createNode("image", {})
204 bgNode.href = bgImageFile
205 bgNode.width = parent.width
206 bgNode.height = parent.height
207 parent.appendChild(bgNode)
208
209 textNode = g_Player.createNode("words", {'rawtextmode':True})
210
211 if not disableMouseFocus:
212 parent.setEventHandler(avg.CURSORUP, avg.MOUSE, self.__onClick)
213 parent.setEventHandler(avg.CURSORUP, avg.TOUCH, self.__onClick)
214
215 parent.appendChild(textNode)
216
217 if focusContext is not None:
218 focusContext.register(self)
219
220 if cursorPixFile is None:
221 crspx = os.path.dirname(__file__)+'/'+DEFAULT_CURSOR_PX
222 else:
223 crspx = cursorPixFile
224
225 cursorNode = g_Player.createNode('image', {'href':crspx})
226 parent.appendChild(cursorNode)
227 self.__flashingCursor = False
228
229 self.__cursorNode = cursorNode
230 self.__textNode = textNode
231 self.__charSize = -1
232 self.setStyle()
233 self.setFocus(False)
234
235 g_Player.setInterval(CURSOR_FLASHING_DELAY, self.__tickFlashCursor)
236
237 self.__lastActivity = 0
238
240 """
241 Returns the ID of the textarea (set on the constructor).
242 Useful with single-entry callbacks
243 """
244 return self.__id
245
246 - def clearText(self):
247 """
248 Clears the text
249 """
250 self.setText('')
251
252 - def setText(self, uString):
253 """
254 Set the text on the TextArea
255 @param uString: an unicode string
256 """
257 self.__textNode.text = uString
258 self.__resetCursorPosition()
259
261 """
262 Get the text stored and displayed on the TextArea
263 """
264 return self.__textNode.text
265
266 - def setStyle(self, font='Arial', size=12, alignment='left', variant='Regular',
267 color='000000', multiline=True, cursorWidth=None, flashingCursor=False):
268 """
269 Set some style parameters of the <words> node of the TextArea
270 @param font: font face
271 @param size: font size in pixels
272 @param alignment: one among 'left', 'right', 'center'
273 @param variant: font variant (eg: 'bold')
274 @param color: RGB hex text color
275 @param multiline: boolean, whether TextArea has to wrap (undefinitely) or stop at full width
276 @param cursorWidth: int, width of the cursor in pixels
277 """
278 self.__textNode.font = font
279 self.__textNode.size = int(size)
280 self.__textNode.alignment = alignment
281 self.__textNode.color = color
282 self.__textNode.variant = variant
283 self.__isMultiline = multiline
284 self.__maxLength = -1
285
286 if multiline:
287 self.__textNode.parawidth = int(self.__parent.width) - self.__border*2
288 else:
289 self.__textNode.parawidth = -1
290
291 self.__textNode.x = self.__border
292 self.__textNode.y = self.__border
293 if cursorWidth is not None:
294 self.__cursorNode.width = cursorWidth
295 else:
296 w = float(size) * CURSOR_WIDTH_PCT / 100.0
297 if w < 1:
298 w = 1
299 self.__cursorNode.width = w
300 self.__flashingCursor = flashingCursor
301 if not flashingCursor:
302 self.__cursorNode.opacity = 1
303
304 self.__resetCursorPosition()
305
306
307 - def setMaxLength(self, maxlen):
308 """
309 Set character limit of the input
310 @param maxlen: max number of character allowed
311 """
312 self.__maxLength = maxlen
313
314 - def clearFocus(self):
315 """
316 Compact form to blur the TextArea
317 """
318 self.__parent.opacity = self.__blurOpacity
319 self.__hasFocus = False
320
321 - def setFocus(self, hasFocus):
322 """
323 Force the focus (or blur) of this TextArea
324 @param hasFocus: boolean
325 """
326 if self.__focusContext is not None:
327 self.__focusContext.resetFocuses()
328
329 if hasFocus:
330 self.__parent.opacity = 1
331 self.__cursorNode.opacity = 1
332 else:
333 self.clearFocus()
334 self.__cursorNode.opacity = 0
335
336 self.__hasFocus = hasFocus
337
338 - def hasFocus(self):
339 """
340 Query the focus status for this TextArea
341 """
342 return self.__hasFocus
343
344 - def onKeyDown(self, keycode):
345 if keycode in KEYCODES_BACKSPACE:
346 self.__removeChar()
347 self.__updateLastActivity()
348 self.__resetCursorPosition()
349
350 elif keycode == KEYCODE_FORMFEED:
351 self.clearText()
352
353 elif keycode not in (0,13,25,63272):
354 self.__appendChar(keycode)
355 self.__updateLastActivity()
356 self.__resetCursorPosition()
357
358 - def __onClick(self, e):
359 if self.__focusContext is not None:
360 if self.__focusContext.isActive():
361 self.setFocus(True)
362 else:
363 self.setFocus(True)
364
365 - def __appendChar(self, keycode):
366 slen = len(self.__textNode.text)
367
368 if slen > 0:
369 lastCharPos = self.__textNode.getGlyphPos(slen-1)
370
371 if (not self.__isMultiline and
372 lastCharPos[0] > self.__parent.width - self.__textNode.size - self.__border * 2):
373 return
374
375
376 if (self.__isMultiline and
377 lastCharPos[1] > self.__parent.height - self.__textNode.size * 2 - self.__border * 2 and
378 lastCharPos[0] > self.__parent.width - self.__textNode.size - self.__border * 2):
379 return
380
381
382 if self.__maxLength > -1 and slen > self.__maxLength:
383 return
384
385 self.__textNode.text = self.__textNode.text + unichr(keycode)
386
387 - def __removeChar(self):
388 self.__textNode.text = self.__textNode.text[0:-1]
389
391 wslen = len(self.__textNode.text)
392
393 if wslen == 0:
394 lastCharPos = (0,0)
395 lastCharExtents = (0,0)
396 else:
397 lastCharPos = self.__textNode.getGlyphPos(wslen-1)
398 lastCharExtents = self.__textNode.getGlyphSize(wslen-1)
399
400 if lastCharExtents[1] > 0:
401 self.__cursorNode.height = lastCharExtents[1] * (1 - CURSOR_PADDING_PCT/100.0)
402 else:
403 self.__cursorNode.height = self.__textNode.size
404
405 self.__cursorNode.x = lastCharPos[0] + lastCharExtents[0] + self.__textNode.size * CURSOR_SPACING_PCT/100.0
406 self.__cursorNode.y = lastCharPos[1] + self.__cursorNode.height * CURSOR_PADDING_PCT/200.0
407
409 self.__lastActivity = time.time()
410
412 if (self.__flashingCursor and
413 self.__hasFocus and
414 time.time() - self.__lastActivity > CURSOR_FLASH_AFTER_INACTIVITY/1000.0):
415 if self.__cursorNode.opacity == 0:
416 self.__cursorNode.opacity = 1
417 else:
418 self.__cursorNode.