Commit before breaking everything
[matches/honours.git] / research / transmission_spectroscopy / simulator / pgu-0.18 / pgu / gui / textarea.py
1 """
2 """
3 import pygame
4 from pygame.locals import *
5
6 from .const import *
7 from . import widget
8
9 class TextArea(widget.Widget):
10     """A multi-line text input.
11     
12     Example:
13         w = TextArea(value="Cuzco the Goat",size=20)
14         w = TextArea("Marbles")
15         w = TextArea("Groucho\nHarpo\nChico\nGummo\nZeppo\n\nMarx", 200, 400, 12)
16     
17     """
18     def __init__(self,value="",width = 120, height = 30, size=20,**params):
19         params.setdefault('cls','input')
20         params.setdefault('width', width)
21         params.setdefault('height', height)
22         
23         widget.Widget.__init__(self,**params)
24         self.value = value                # The value of the TextArea
25         self.pos = len(str(value))        # The position of the cursor
26         self.vscroll = 0                # The number of lines that the TextArea is currently scrolled
27         self.font = self.style.font        # The font used for rendering the text
28         self.cursor_w = 2                 # Cursor width (NOTE: should be in a style)
29         w,h = self.font.size("e"*size)    
30         if not self.style.height: self.style.height = h
31         if not self.style.width: self.style.width = w
32     
33 ## BUG: This causes textarea to grow every time table._Table_td calculates its
34 ## size.
35 ##    def resize(self,width=None,height=None):
36 ##        if (width != None) and (height != None):
37 ##            print 'TextArea RESIZE'
38 ##            self.rect = pygame.Rect(self.rect.x, self.rect.y, width, height)
39 ##        return self.rect.w, self.rect.h
40         
41     def paint(self,s):
42         
43         # 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.
44         max_line_w = self.rect.w - 20
45                 
46         # Update the line allocation for the box's value
47         self.doLines(max_line_w)
48         
49         # Make sure that the vpos and hpos of the cursor is set properly
50         self.updateCursorPos()
51
52         # Make sure that we're scrolled vertically such that the cursor is visible
53         if (self.vscroll < 0):
54             self.vscroll = 0
55         if (self.vpos < self.vscroll):
56             self.vscroll = self.vpos
57         elif ((self.vpos - self.vscroll + 1) * self.line_h > self.rect.h):
58             self.vscroll = - (self.rect.h / self.line_h - self.vpos - 1)
59
60         # Blit each of the lines in turn
61         cnt = 0
62         for line in self.lines:
63             line_pos = (0, (cnt - self.vscroll) * self.line_h)
64             if (line_pos[1] >= 0) and (line_pos[1] < self.rect.h):
65                 s.blit( self.font.render(line, 1, self.style.color), line_pos )
66             cnt += 1
67         
68         # If the textarea is focused, then also show the cursor
69         if self.container.myfocus is self:
70             r = self.getCursorRect()
71             s.fill(self.style.color,r)
72     
73     # This function updates self.vpos and self.hpos based on self.pos
74     def updateCursorPos(self):
75         self.vpos = 0 # Reset the current line that the cursor is on
76         self.hpos = 0
77         
78         line_cnt = 0
79         char_cnt = 0
80
81         for line in self.lines:
82             line_char_start = char_cnt # The number of characters at the start of the line
83             
84             # Keep track of the character count for words
85             char_cnt += len(line)
86             
87             # 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
88             if (char_cnt > self.pos):
89                 self.vpos = line_cnt
90                 self.hpos = self.pos - line_char_start
91
92                 break # Now that we know where our cursor is, we exit the loop
93
94             line_cnt += 1
95         
96         if (char_cnt <= self.pos) and (len(self.lines) > 0):
97             self.vpos = len(self.lines) - 1
98             self.hpos = len(self.lines[ self.vpos ] )
99
100     # Returns a rectangle that is of the size and position of where the cursor is drawn    
101     def getCursorRect(self):
102         lw = 0
103         if (len(self.lines) > 0):
104             lw, lh = self.font.size( self.lines[ self.vpos ][ 0:self.hpos ] )
105             
106         r = pygame.Rect(lw, (self.vpos - self.vscroll) * self.line_h, self.cursor_w, self.line_h)
107         return r
108     
109     # This function sets the cursor position according to an x/y value (such as by from a mouse click)
110     def setCursorByXY(self, pos):
111         (x, y) = pos
112         self.vpos = ((int) (y / self.line_h)) + self.vscroll
113         if (self.vpos >= len(self.lines)):
114             self.vpos = len(self.lines) - 1
115             
116         currentLine = self.lines[ self.vpos ]
117         
118         for cnt in range(0, len(currentLine) ):
119             self.hpos = cnt
120             lw, lh = self.font.size( currentLine[ 0:self.hpos + 1 ] )
121             if (lw > x):
122                 break
123         
124         lw, lh = self.font.size( currentLine )
125         if (lw < x):
126             self.hpos = len(currentLine)
127             
128         self.setCursorByHVPos()
129         
130     # This function sets the cursor position by the horizontal/vertical cursor position.    
131     def setCursorByHVPos(self):
132         line_cnt = 0
133         char_cnt = 0
134         
135         for line in self.lines:
136             line_char_start = char_cnt # The number of characters at the start of the line
137             
138             # Keep track of the character count for words
139             char_cnt += len(line)
140
141             # If we're on the proper line
142             if (line_cnt == self.vpos):
143                 # Make sure that we're not trying to go over the edge of the current line
144                 if ( self.hpos > len(line) ):
145                     self.hpos = len(line) - 1
146                 # Set the cursor position
147                 self.pos = line_char_start + self.hpos
148                 break    # Now that we've set our cursor position, we exit the loop
149                 
150             line_cnt += 1        
151     
152     # Splits up the text found in the control's value, and assigns it into the lines array
153     def doLines(self, max_line_w):
154         self.line_h = 10
155         self.lines = [] # Create an empty starter list to start things out.
156         
157         inx = 0
158         line_start = 0
159         while inx >= 0:
160             # Find the next breakable whitespace
161             # HACK: Find a better way to do this to include tabs and system characters and whatnot.
162             prev_word_start = inx # Store the previous whitespace
163             spc_inx = self.value.find(' ', inx+1)
164             nl_inx = self.value.find('\n', inx+1)
165             
166             if (min(spc_inx, nl_inx) == -1):
167                 inx = max(spc_inx, nl_inx)
168             else:
169                 inx = min(spc_inx, nl_inx)
170                 
171             # Measure the current line
172             lw, self.line_h = self.font.size( self.value[ line_start : inx ] )
173             
174             # If we exceeded the max line width, then create a new line
175             if (lw > max_line_w):
176                 #Fall back to the previous word start
177                 self.lines.append(self.value[ line_start : prev_word_start + 1 ])
178                 line_start = prev_word_start + 1
179                 # TODO: Check for extra-long words here that exceed the length of a line, to wrap mid-word
180                 
181             # If we reached the end of our text
182             if (inx < 0):
183                 # Then make sure we added the last of the line
184                 if (line_start < len( self.value ) ):
185                     self.lines.append( self.value[ line_start : len( self.value ) ] )
186                 else:
187                     self.lines.append('')
188             # If we reached a hard line break
189             elif (self.value[inx] == "\n"):
190                 # Then make a line break here as well.
191                 newline = self.value[ line_start : inx + 1 ]
192                 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.
193                 self.lines.append( newline )
194                 
195                 line_start = inx + 1
196             else:
197                 # Otherwise, we just continue progressing to the next space
198                 pass
199         
200     def _setvalue(self,v):
201         self.__dict__['value'] = v
202         self.send(CHANGE)
203     
204     def event(self,e):
205         used = None
206         if e.type == KEYDOWN:
207             used = True
208             if e.key == K_BACKSPACE:
209                 if self.pos:
210                     self._setvalue(self.value[:self.pos-1] + self.value[self.pos:])
211                     self.pos -= 1
212             elif e.key == K_DELETE:
213                 if len(self.value) > self.pos:
214                     self._setvalue(self.value[:self.pos] + self.value[self.pos+1:])
215             elif e.key == K_HOME:
216                 # Find the previous newline
217                 newPos = self.value.rfind('\n', 0, self.pos)
218                 if (newPos >= 0):
219                     self.pos = newPos
220             elif e.key == K_END:
221                 # Find the previous newline
222                 newPos = self.value.find('\n', self.pos, len(self.value) )
223                 if (newPos >= 0):
224                     self.pos = newPos
225             elif e.key == K_LEFT:
226                 if self.pos > 0: self.pos -= 1
227 #                used = True
228             elif e.key == K_RIGHT:
229                 if self.pos < len(self.value): self.pos += 1
230 #                used = True
231             elif e.key == K_UP:
232                 self.vpos -= 1
233                 self.setCursorByHVPos()
234             elif e.key == K_DOWN:
235                 self.vpos += 1
236                 self.setCursorByHVPos()
237             # The following return/tab keys are standard for PGU widgets, but I took them out here to facilitate multi-line text editing
238 #            elif e.key == K_RETURN:
239 #                self.next()
240 #            elif e.key == K_TAB:
241 #                pass                
242             else:
243                 #c = str(e.unicode)
244                 used = None
245                 try:
246                     if (e.key == K_RETURN):
247                         c = "\n"
248                     elif (e.key == K_TAB):
249                         c = "  "
250                     else:
251                         if (type(e.unicode) == str):
252                             c = e.unicode
253                         else:
254                             c = (e.unicode).encode('latin-1')
255                     if c:
256                         used = True
257                         self._setvalue(self.value[:self.pos] + c + self.value[self.pos:])
258                         self.pos += len(c)
259                 except: #ignore weird characters
260                     pass
261             self.repaint()
262         elif e.type == MOUSEBUTTONDOWN:
263             self.setCursorByXY(e.pos)
264             self.repaint()
265             
266         elif e.type == FOCUS:
267             self.repaint()
268         elif e.type == BLUR:
269             self.repaint()
270         
271         self.pcls = ""
272         if self.container.myfocus is self: self.pcls = "focus"
273         
274         return used
275     
276     def __setattr__(self,k,v):
277         if k == 'value':
278             if v == None: v = ''
279             v = str(v)
280             self.pos = len(v)
281         _v = self.__dict__.get(k,NOATTR)
282         self.__dict__[k]=v
283         if k == 'value' and _v != NOATTR and _v != v: 
284             self.send(CHANGE)
285             self.repaint()
286             
287 # The first version of this code was done by Clint Herron, and is a modified version of input.py (by Phil Hassey).
288 # It is under the same license as the rest of the PGU library.
289

UCC git Repository :: git.ucc.asn.au