From 6eb30b09566a53ef510532f2a1705d7fc22985a8 Mon Sep 17 00:00:00 2001 From: Tony Anderson Date: Mon, 22 Jun 2009 14:04:24 +0000 Subject: initial commit --- (limited to 'pgu/gui/textarea.py') diff --git a/pgu/gui/textarea.py b/pgu/gui/textarea.py new file mode 100644 index 0000000..667076a --- /dev/null +++ b/pgu/gui/textarea.py @@ -0,0 +1,287 @@ +""" +""" +import pygame +from pygame.locals import * + +from const import * +import widget + +class TextArea(widget.Widget): + """A multi-line text input. + +
TextArea(value="",width = 120, height = 30, size=20)
+ +
+
value
initial text +
size
size for the text box, in characters +
+ + Example + + w = TextArea(value="Cuzco the Goat",size=20) + + w = TextArea("Marbles") + + w = TextArea("Groucho\nHarpo\nChico\nGummo\nZeppo\n\nMarx", 200, 400, 12) + + + """ + def __init__(self,value="",width = 120, height = 30, size=20,**params): + params.setdefault('cls','input') + params.setdefault('width', width) + params.setdefault('height', height) + + widget.Widget.__init__(self,**params) + self.value = value # The value of the TextArea + self.pos = len(str(value)) # The position of the cursor + self.vscroll = 0 # The number of lines that the TextArea is currently scrolled + self.font = self.style.font # The font used for rendering the text + self.cursor_w = 2 # Cursor width (NOTE: should be in a style) + w,h = self.font.size("e"*size) + if not self.style.height: self.style.height = h + if not self.style.width: self.style.width = w + + def resize(self,width=None,height=None): + if (width != None) and (height != None): + self.rect = pygame.Rect(self.rect.x, self.rect.y, width, height) + return self.rect.w, self.rect.h + + def paint(self,s): + + # 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. + max_line_w = self.rect.w - 20 + + # Update the line allocation for the box's value + self.doLines(max_line_w) + + # Make sure that the vpos and hpos of the cursor is set properly + self.updateCursorPos() + + # Make sure that we're scrolled vertically such that the cursor is visible + if (self.vscroll < 0): + self.vscroll = 0 + if (self.vpos < self.vscroll): + self.vscroll = self.vpos + elif ((self.vpos - self.vscroll + 1) * self.line_h > self.rect.h): + self.vscroll = - (self.rect.h / self.line_h - self.vpos - 1) + + # Blit each of the lines in turn + cnt = 0 + for line in self.lines: + line_pos = (0, (cnt - self.vscroll) * self.line_h) + if (line_pos[1] >= 0) and (line_pos[1] < self.rect.h): + s.blit( self.font.render(line, 1, self.style.color), line_pos ) + cnt += 1 + + # If the textarea is focused, then also show the cursor + if self.container.myfocus is self: + r = self.getCursorRect() + s.fill(self.style.color,r) + + # This function updates self.vpos and self.hpos based on self.pos + def updateCursorPos(self): + self.vpos = 0 # Reset the current line that the cursor is on + self.hpos = 0 + + line_cnt = 0 + char_cnt = 0 + + for line in self.lines: + line_char_start = char_cnt # The number of characters at the start of the line + + # Keep track of the character count for words + char_cnt += len(line) + + # 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 + if (char_cnt > self.pos): + self.vpos = line_cnt + self.hpos = self.pos - line_char_start + + break # Now that we know where our cursor is, we exit the loop + + line_cnt += 1 + + if (char_cnt <= self.pos) and (len(self.lines) > 0): + self.vpos = len(self.lines) - 1 + self.hpos = len(self.lines[ self.vpos ] ) + + # Returns a rectangle that is of the size and position of where the cursor is drawn + def getCursorRect(self): + lw = 0 + if (len(self.lines) > 0): + lw, lh = self.font.size( self.lines[ self.vpos ][ 0:self.hpos ] ) + + r = pygame.Rect(lw, (self.vpos - self.vscroll) * self.line_h, self.cursor_w, self.line_h) + return r + + # This function sets the cursor position according to an x/y value (such as by from a mouse click) + def setCursorByXY(self, (x, y)): + self.vpos = ((int) (y / self.line_h)) + self.vscroll + if (self.vpos >= len(self.lines)): + self.vpos = len(self.lines) - 1 + + currentLine = self.lines[ self.vpos ] + + for cnt in range(0, len(currentLine) ): + self.hpos = cnt + lw, lh = self.font.size( currentLine[ 0:self.hpos + 1 ] ) + if (lw > x): + break + + lw, lh = self.font.size( currentLine ) + if (lw < x): + self.hpos = len(currentLine) + + self.setCursorByHVPos() + + # This function sets the cursor position by the horizontal/vertical cursor position. + def setCursorByHVPos(self): + line_cnt = 0 + char_cnt = 0 + + for line in self.lines: + line_char_start = char_cnt # The number of characters at the start of the line + + # Keep track of the character count for words + char_cnt += len(line) + + # If we're on the proper line + if (line_cnt == self.vpos): + # Make sure that we're not trying to go over the edge of the current line + if ( self.hpos >= len(line) ): + self.hpos = len(line) - 1 + # Set the cursor position + self.pos = line_char_start + self.hpos + break # Now that we've set our cursor position, we exit the loop + + line_cnt += 1 + + # Splits up the text found in the control's value, and assigns it into the lines array + def doLines(self, max_line_w): + self.line_h = 10 + self.lines = [] # Create an empty starter list to start things out. + + inx = 0 + line_start = 0 + while inx >= 0: + # Find the next breakable whitespace + # HACK: Find a better way to do this to include tabs and system characters and whatnot. + prev_word_start = inx # Store the previous whitespace + spc_inx = self.value.find(' ', inx+1) + nl_inx = self.value.find('\n', inx+1) + + if (min(spc_inx, nl_inx) == -1): + inx = max(spc_inx, nl_inx) + else: + inx = min(spc_inx, nl_inx) + + # Measure the current line + lw, self.line_h = self.font.size( self.value[ line_start : inx ] ) + + # If we exceeded the max line width, then create a new line + if (lw > max_line_w): + #Fall back to the previous word start + self.lines.append(self.value[ line_start : prev_word_start + 1 ]) + line_start = prev_word_start + 1 + # TODO: Check for extra-long words here that exceed the length of a line, to wrap mid-word + + # If we reached the end of our text + if (inx < 0): + # Then make sure we added the last of the line + if (line_start < len( self.value ) ): + self.lines.append( self.value[ line_start : len( self.value ) ] ) + # If we reached a hard line break + elif (self.value[inx] == "\n"): + # Then make a line break here as well. + newline = self.value[ line_start : inx + 1 ] + 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. + self.lines.append( newline ) + + line_start = inx + 1 + else: + # Otherwise, we just continue progressing to the next space + pass + + def _setvalue(self,v): + self.__dict__['value'] = v + self.send(CHANGE) + + def event(self,e): + used = None + if e.type == KEYDOWN: + if e.key == K_BACKSPACE: + if self.pos: + self._setvalue(self.value[:self.pos-1] + self.value[self.pos:]) + self.pos -= 1 + elif e.key == K_DELETE: + if len(self.value) > self.pos: + self._setvalue(self.value[:self.pos] + self.value[self.pos+1:]) + elif e.key == K_HOME: + # Find the previous newline + newPos = self.value.rfind('\n', 0, self.pos) + if (newPos >= 0): + self.pos = newPos + elif e.key == K_END: + # Find the previous newline + newPos = self.value.find('\n', self.pos, len(self.value) ) + if (newPos >= 0): + self.pos = newPos + elif e.key == K_LEFT: + if self.pos > 0: self.pos -= 1 + used = True + elif e.key == K_RIGHT: + if self.pos < len(self.value): self.pos += 1 + used = True + elif e.key == K_UP: + self.vpos -= 1 + self.setCursorByHVPos() + elif e.key == K_DOWN: + self.vpos += 1 + self.setCursorByHVPos() + # The following return/tab keys are standard for PGU widgets, but I took them out here to facilitate multi-line text editing +# elif e.key == K_RETURN: +# self.next() +# elif e.key == K_TAB: +# pass + else: + #c = str(e.unicode) + try: + if (e.key == K_RETURN): + c = "\n" + elif (e.key == K_TAB): + c = " " + else: + c = (e.unicode).encode('latin-1') + if c: + self._setvalue(self.value[:self.pos] + c + self.value[self.pos:]) + self.pos += len(c) + except: #ignore weird characters + pass + self.repaint() + elif e.type == MOUSEBUTTONDOWN: + self.setCursorByXY(e.pos) + self.repaint() + + elif e.type == FOCUS: + self.repaint() + elif e.type == BLUR: + self.repaint() + + self.pcls = "" + if self.container.myfocus is self: self.pcls = "focus" + + return used + + def __setattr__(self,k,v): + if k == 'value': + if v == None: v = '' + v = str(v) + self.pos = len(v) + _v = self.__dict__.get(k,NOATTR) + self.__dict__[k]=v + if k == 'value' and _v != NOATTR and _v != v: + self.send(CHANGE) + self.repaint() + +# The first version of this code was done by Clint Herron, and is a modified version of input.py (by Phil Hassey). +# It is under the same license as the rest of the PGU library. \ No newline at end of file -- cgit v0.9.1