4 from pygame
.locals import *
9 class TextArea(widget
.Widget
):
10 """A multi-line text input.
12 <pre>TextArea(value="",width = 120, height = 30, size=20)</pre>
15 <dt>value<dd>initial text
16 <dt>size<dd>size for the text box, in characters
19 <strong>Example</strong>
21 w = TextArea(value="Cuzco the Goat",size=20)
23 w = TextArea("Marbles")
25 w = TextArea("Groucho\nHarpo\nChico\nGummo\nZeppo\n\nMarx", 200, 400, 12)
29 def __init__(self
,value
="",width
= 120, height
= 30, size
=20,**params
):
30 params
.setdefault('cls','input')
31 params
.setdefault('width', width
)
32 params
.setdefault('height', height
)
34 widget
.Widget
.__init
__(self
,**params
)
35 self
.value
= value
# The value of the TextArea
36 self
.pos
= len(str(value
)) # The position of the cursor
37 self
.vscroll
= 0 # The number of lines that the TextArea is currently scrolled
38 self
.font
= self
.style
.font
# The font used for rendering the text
39 self
.cursor_w
= 2 # Cursor width (NOTE: should be in a style)
40 w
,h
= self
.font
.size("e"*size
)
41 if not self
.style
.height
: self
.style
.height
= h
42 if not self
.style
.width
: self
.style
.width
= w
44 def resize(self
,width
=None,height
=None):
45 if (width
!= None) and (height
!= None):
46 self
.rect
= pygame
.Rect(self
.rect
.x
, self
.rect
.y
, width
, height
)
47 return self
.rect
.w
, self
.rect
.h
51 # TODO: What's up with this 20 magic number? It's the margin of the left and right sides, but I'm not sure how this should be gotten other than by trial and error.
52 max_line_w
= self
.rect
.w
- 20
54 # Update the line allocation for the box's value
55 self
.doLines(max_line_w
)
57 # Make sure that the vpos and hpos of the cursor is set properly
58 self
.updateCursorPos()
60 # Make sure that we're scrolled vertically such that the cursor is visible
61 if (self
.vscroll
< 0):
63 if (self
.vpos
< self
.vscroll
):
64 self
.vscroll
= self
.vpos
65 elif ((self
.vpos
- self
.vscroll
+ 1) * self
.line_h
> self
.rect
.h
):
66 self
.vscroll
= - (self
.rect
.h
/ self
.line_h
- self
.vpos
- 1)
68 # Blit each of the lines in turn
70 for line
in self
.lines
:
71 line_pos
= (0, (cnt
- self
.vscroll
) * self
.line_h
)
72 if (line_pos
[1] >= 0) and (line_pos
[1] < self
.rect
.h
):
73 s
.blit( self
.font
.render(line
, 1, self
.style
.color
), line_pos
)
76 # If the textarea is focused, then also show the cursor
77 if self
.container
.myfocus
is self
:
78 r
= self
.getCursorRect()
79 s
.fill(self
.style
.color
,r
)
81 # This function updates self.vpos and self.hpos based on self.pos
82 def updateCursorPos(self
):
83 self
.vpos
= 0 # Reset the current line that the cursor is on
89 for line
in self
.lines
:
90 line_char_start
= char_cnt
# The number of characters at the start of the line
92 # Keep track of the character count for words
95 # If our cursor count is still less than the cursor position, then we can update our cursor line to assume that it's at least on this line
96 if (char_cnt
> self
.pos
):
98 self
.hpos
= self
.pos
- line_char_start
100 break # Now that we know where our cursor is, we exit the loop
104 if (char_cnt
<= self
.pos
) and (len(self
.lines
) > 0):
105 self
.vpos
= len(self
.lines
) - 1
106 self
.hpos
= len(self
.lines
[ self
.vpos
] )
108 # Returns a rectangle that is of the size and position of where the cursor is drawn
109 def getCursorRect(self
):
111 if (len(self
.lines
) > 0):
112 lw
, lh
= self
.font
.size( self
.lines
[ self
.vpos
][ 0:self
.hpos
] )
114 r
= pygame
.Rect(lw
, (self
.vpos
- self
.vscroll
) * self
.line_h
, self
.cursor_w
, self
.line_h
)
117 # This function sets the cursor position according to an x/y value (such as by from a mouse click)
118 def setCursorByXY(self
, (x
, y
)):
119 self
.vpos
= ((int) (y
/ self
.line_h
)) + self
.vscroll
120 if (self
.vpos
>= len(self
.lines
)):
121 self
.vpos
= len(self
.lines
) - 1
123 currentLine
= self
.lines
[ self
.vpos
]
125 for cnt
in range(0, len(currentLine
) ):
127 lw
, lh
= self
.font
.size( currentLine
[ 0:self
.hpos
+ 1 ] )
131 lw
, lh
= self
.font
.size( currentLine
)
133 self
.hpos
= len(currentLine
)
135 self
.setCursorByHVPos()
137 # This function sets the cursor position by the horizontal/vertical cursor position.
138 def setCursorByHVPos(self
):
142 for line
in self
.lines
:
143 line_char_start
= char_cnt
# The number of characters at the start of the line
145 # Keep track of the character count for words
146 char_cnt
+= len(line
)
148 # If we're on the proper line
149 if (line_cnt
== self
.vpos
):
150 # Make sure that we're not trying to go over the edge of the current line
151 if ( self
.hpos
>= len(line
) ):
152 self
.hpos
= len(line
) - 1
153 # Set the cursor position
154 self
.pos
= line_char_start
+ self
.hpos
155 break # Now that we've set our cursor position, we exit the loop
159 # Splits up the text found in the control's value, and assigns it into the lines array
160 def doLines(self
, max_line_w
):
162 self
.lines
= [] # Create an empty starter list to start things out.
167 # Find the next breakable whitespace
168 # HACK: Find a better way to do this to include tabs and system characters and whatnot.
169 prev_word_start
= inx
# Store the previous whitespace
170 spc_inx
= self
.value
.find(' ', inx
+1)
171 nl_inx
= self
.value
.find('\n', inx
+1)
173 if (min(spc_inx
, nl_inx
) == -1):
174 inx
= max(spc_inx
, nl_inx
)
176 inx
= min(spc_inx
, nl_inx
)
178 # Measure the current line
179 lw
, self
.line_h
= self
.font
.size( self
.value
[ line_start
: inx
] )
181 # If we exceeded the max line width, then create a new line
182 if (lw
> max_line_w
):
183 #Fall back to the previous word start
184 self
.lines
.append(self
.value
[ line_start
: prev_word_start
+ 1 ])
185 line_start
= prev_word_start
+ 1
186 # TODO: Check for extra-long words here that exceed the length of a line, to wrap mid-word
188 # If we reached the end of our text
190 # Then make sure we added the last of the line
191 if (line_start
< len( self
.value
) ):
192 self
.lines
.append( self
.value
[ line_start
: len( self
.value
) ] )
193 # If we reached a hard line break
194 elif (self
.value
[inx
] == "\n"):
195 # Then make a line break here as well.
196 newline
= self
.value
[ line_start
: inx
+ 1 ]
197 newline
= newline
.replace("\n", " ") # HACK: We know we have a newline character, which doesn't print nicely, so make it into a space. Comment this out to see what I mean.
198 self
.lines
.append( newline
)
202 # Otherwise, we just continue progressing to the next space
205 def _setvalue(self
,v
):
206 self
.__dict
__['value'] = v
211 if e
.type == KEYDOWN
:
212 if e
.key
== K_BACKSPACE
:
214 self
._setvalue
(self
.value
[:self
.pos
-1] + self
.value
[self
.pos
:])
216 elif e
.key
== K_DELETE
:
217 if len(self
.value
) > self
.pos
:
218 self
._setvalue
(self
.value
[:self
.pos
] + self
.value
[self
.pos
+1:])
219 elif e
.key
== K_HOME
:
220 # Find the previous newline
221 newPos
= self
.value
.rfind('\n', 0, self
.pos
)
225 # Find the previous newline
226 newPos
= self
.value
.find('\n', self
.pos
, len(self
.value
) )
229 elif e
.key
== K_LEFT
:
230 if self
.pos
> 0: self
.pos
-= 1
232 elif e
.key
== K_RIGHT
:
233 if self
.pos
< len(self
.value
): self
.pos
+= 1
237 self
.setCursorByHVPos()
238 elif e
.key
== K_DOWN
:
240 self
.setCursorByHVPos()
241 # The following return/tab keys are standard for PGU widgets, but I took them out here to facilitate multi-line text editing
242 # elif e.key == K_RETURN:
244 # elif e.key == K_TAB:
249 if (e
.key
== K_RETURN
):
251 elif (e
.key
== K_TAB
):
254 c
= (e
.unicode).encode('latin-1')
256 self
._setvalue
(self
.value
[:self
.pos
] + c
+ self
.value
[self
.pos
:])
258 except: #ignore weird characters
261 elif e
.type == MOUSEBUTTONDOWN
:
262 self
.setCursorByXY(e
.pos
)
265 elif e
.type == FOCUS
:
271 if self
.container
.myfocus
is self
: self
.pcls
= "focus"
275 def __setattr__(self
,k
,v
):
280 _v
= self
.__dict
__.get(k
,NOATTR
)
282 if k
== 'value' and _v
!= NOATTR
and _v
!= v
:
286 # The first version of this code was done by Clint Herron, and is a modified version of input.py (by Phil Hassey).
287 # It is under the same license as the rest of the PGU library.