#!/usr/bin/env python

import pygame, pygame.display, pygame.event, pygame.time, pygame.font, pygame.draw
from pygame.locals import *
import math, string

# initialize PyGame
#pygame.init()

# initialize modules manually to avoid initing pygame.sound
pygame.display.init()
pygame.font.init()

# setup screen
screen = pygame.display.set_mode( (640, 480), DOUBLEBUF )
pygame.display.set_caption( 'Client' )
# screen updates
DRAWEVENT = USEREVENT + 1
pygame.time.set_timer( DRAWEVENT, 100 ) #33 == 30fps

class ClientClass:
	def __init__( self, screen ):
		
		# filter events
		badevents = [NOEVENT, ACTIVEEVENT, JOYAXISMOTION, JOYBALLMOTION, JOYHATMOTION, JOYBUTTONDOWN ,JOYBUTTONUP, VIDEORESIZE, SYSWMEVENT, NUMEVENTS]
		goodevents = [KEYDOWN, KEYUP, MOUSEMOTION, MOUSEBUTTONDOWN, MOUSEBUTTONUP, QUIT, DRAWEVENT ]
		pygame.event.set_blocked( badevents )
		
		# data
		self.running = 1
		self.screen = screen
		self.widgets = []
		self.mousedest = None
		self.keydest = None
	
		# widgets
		self.page = PageClass( self, (50, 50, 400, 250), (64, 64, 64, 0) )
		self.widgets.append( self.page )
		self.edit = EditClass( self, (50, 350, 400, 25) )
		self.widgets.append( self.edit )
		self.grabkey( self.edit )
	
	def enterpressed( self, widget ):
		self.page.append( widget.text )
		widget.text = ""
	
	def grabmouse( self, widget ):
		self.mousedest = widget
	
	def grabkey( self, widget ):
		self.keydest = widget
	
	def dispatch( self, event ):
		"""Send the event to the widget under the mouse"""
		for w in self.widgets:
			if w.rect.collidepoint( event.pos ):
				w.eventproc( event )
				break
	def loop( self ):
		while self.running:
			# get the next event
			event = pygame.event.wait()
			
			# mouse events
			if event.type == MOUSEMOTION or event.type == MOUSEBUTTONDOWN or event.type == MOUSEBUTTONUP:
				# mouse is grabbed
				if self.mousedest != None:
					self.mousedest.eventproc( event )
				# not grabbed
				else: self.dispatch( event )
			
			# keyboard events
			if event.type == KEYDOWN or event.type == KEYUP:
				# keyboard is grabbed
				if self.keydest != None:
					self.keydest.eventproc( event )
			
			# user pressed the X button to close the window
			elif event.type == QUIT: self.running = 0
			
			# DRAWEVENT comes regularly for a steady framerate
			elif event.type == DRAWEVENT:
				for w in self.widgets:
					w.draw( self.screen )
				pygame.display.update()

class PageClass:
	def eventproc( self, event ):
		# button is clicked
		if event.type == MOUSEBUTTONDOWN:
			(x, y) = event.pos
			# click is on the scroll shuttle
			if self.shuttlerect.collidepoint( event.pos ):
				# distance from the click to the bottom of the shuttle
				self.clickoffset = self.shuttlerect.bottom - y
				# begin scrolling
				self.manager.grabmouse( self )
				self.scrollgrabbed = 1
		
		# button is released
		if event.type == MOUSEBUTTONUP:
			# currently scrolling
			if self.scrollgrabbed == 1:
				# final scroll position
				self.scroll( event.pos )
				# done scrolling
				self.manager.grabmouse( None )
				self.scrollgrabbed = 0
				del self.clickoffset
		
		# mouse cursor moves
		if event.type == MOUSEMOTION:
			# scroll bar is grabbed
			if self.scrollgrabbed == 1: self.scroll( event.pos )
		
		if event.type == KEYDOWN:
			self.append( self.text )
			self.num += 1
			self.text = "%s %d" % (self.text, self.num)
		
	def __init__( self, manager, rect, background=(0, 0, 0, 0), fontsize=24, maxlines=40 ):
		rect = Rect( rect )
		self.manager = manager
		self.background = background
		self.font = pygame.font.Font( None, fontsize )
		self.dirty = 1
		self.rect = Rect(rect)
		self.text = "1"
		self.num = 1
		
		# Scroll bar
		scrollwidth = 10
		self.scrollgrabbed = 0
		
		# Page
		self.lines = []
		self.showline = 0
		self.maxlines = maxlines
		
		# page rect
		rect.width -= scrollwidth
		self.pagerect = Rect( rect )
		
		# scroll rect
		rect.width = scrollwidth
		rect.right = self.rect.right
		self.scrollrect = Rect( rect )

		# shuttle rect
		self.shuttlerect = Rect( rect )
		self.shuttlerect.height /= 2
		
		# intial text
		self.append( " " )

	def append( self, newtext, foreground=(0xFF, 0xFF, 0xFF, 0xFF) ):
		# expand tabs
		newtext = newtext.expandtabs()
		
		# append each line separately
		for line in newtext.splitlines():
				self.appendline( line, foreground )
	
	def fits( self, text ):
		# size of the text
		(fw, fh) = self.font.size( text )
		# too wide
		if fw > self.pagerect.width: return 0
		return 1
	
	def appendline( self, newtext, foreground ):
		# remove trailing whitespace
		newtext = newtext.rstrip()
		
		# find largest piece of text that will fit on one line
		pt = len( newtext )
		while not self.fits( newtext[:pt] ):
			# find the last space
			pt = newtext.rfind( " ", 0, pt )
			# no spaces left
			if pt == -1: break
		
		# first word doesn't fit
		if pt == -1:
			# break word
			pt = len( newtext )
			while not self.fits( newtext[:pt] ):
				# not even one letter will fit
				if pt == 1: raise Exception
				# move the break one letter to the left
				pt -= 1
		
		# add an extra space in case the line was empty
		thistext = newtext[:pt] + " "
		
		# leftover wrapped text
		newtext = newtext[pt:]
		
		# make a new surface
		surface = self.font.render( thistext, 1, foreground, self.background )
		self.lineheight = surface.get_height()
		self.lines.insert( 0, surface )
		
		# too many lines
		while len( self.lines ) > self.maxlines:
			# discard last line
			del self.lines[-1]
		
		# if not scrolled to the bottom
		if self.showline != 0:
			# keep scroll bar on same line
			self.showline += 1
			# if line disappears then stay at the top
			if self.showline >= len( self.lines ): self.showline = len( self.lines ) - 1
		
		# resize scrollbar
		self.scrollcheck()
		
		# leftover wrapped text
		if len( newtext ) > 0:
			self.appendline( newtext, foreground )
		
		# need screen update
		self.dirty = 1
	
	def scrollcheck( self ):
		#print ""
		
		# will need redraw
		self.dirty = 1
		
		visiblelines = self.pagerect.height / self.lineheight
		#print "visiblelines", visiblelines
		
		#print "lines", len( self.lines )
		
		# nothing to scroll
		if len( self.lines ) <= visiblelines:
			self.shuttlerect = Rect( self.scrollrect )
			return
		
		# last line is below top
		if self.showline > len( self.lines ) - visiblelines:
			self.showline = len( self.lines ) - visiblelines
		
		# shuttle size
		self.shuttlerect.height = int( (float( visiblelines ) / float( len( self.lines ) )) * float( self.pagerect.height ) )
		#print "shuttle height", self.shuttlerect.height
		
		linetravel = len( self.lines ) - visiblelines
		#print "linetravel", linetravel
		
		travel = self.scrollrect.height - self.shuttlerect.height
		#print "travel", travel
		
		x = int( (float( self.showline ) / float( linetravel )) * float( travel ) )
		#print "x", x
		
		self.shuttlerect.bottom = self.scrollrect.bottom - x
		#print "shuttle top", self.shuttlerect.top

		# keep shuttle on scroll bar
		self.shuttlerect.clamp_ip( self.scrollrect )
		#print "shuttle", self.shuttlerect
	
	def scroll( self, (x, y) ):
		#print ""
		# shuttle must track mouse
		self.shuttlerect.bottom = self.clickoffset + y
		# keep shuttle on scroll bar
		self.shuttlerect.clamp_ip( self.scrollrect )
		#print "shuttle", self.shuttlerect
		
		# need redraw
		self.dirty = 1
		
		#print "lines", len( self.lines )
		
		travel = self.scrollrect.height - self.shuttlerect.height
		#print "travel", travel
		visiblelines = self.pagerect.height / self.lineheight
		#print "visiblelines", visiblelines
		
		if len( self.lines ) <= visiblelines:
			self.showline = 0
			return
			
		linetravel = len( self.lines ) - visiblelines
		#print "linetravel", linetravel
		x = self.shuttlerect.top - self.scrollrect.top
		#print "x", x
		l = int( (float( x ) / float( travel )) * float( linetravel ) )
		#print "l", l
		
		self.showline = len( self.lines ) - visiblelines - l
		#print "showline", self.showline
	
	def draw( self, screen ):
		# only draw if it's required
		if self.dirty == 0: return
		
		# clip our drawing
		screen.set_clip( self.rect )
		# clear the page area
#		screen.fill( self.background, self.pagerect )
		screen.fill( (0xAF, 0x8F, 0x8F, 0xFF), self.pagerect )
		
		# start at the bottom
		y = self.pagerect.bottom
		# draw each line
		for surface in self.lines[self.showline:]:
			sw = surface.get_width()
			sh = surface.get_height()
			# upper left corner
			y -= sh
			# blit it to the screen
			screen.blit( surface, (self.rect.left, y) )
			# drew the topmost visible line
			if y <= self.rect.top: break
		
		#draw scroll bar
		screen.fill( (0x8F, 0x8F, 0x8F, 0xFF), self.scrollrect )
		screen.fill( (0xAF, 0xAF, 0xAF, 0xFF), self.shuttlerect )
		
		# drawing is done
		self.dirty = 0

class EditClass:
	def eventproc( self, event ):
		# mouse click
		if event.type == MOUSEBUTTONDOWN:
			# grab keyboard
			self.manager.grabkey( self )
		
		# key press
		elif event.type == KEYDOWN:
			# backspace
			if event.key == K_BACKSPACE:
				# remove last character
				self.newtext( self.text[:-1] )
			# enter
			elif event.key == K_RETURN:
				# report it to the manager
				self.manager.enterpressed( self )
				self.maketext()
			# some other key
			else:
				# buffer is at max
				if len( self.text ) >= self.maxchars: return
				
				# convert key to character code
				character = str( event.unicode )
				
				# character is not allowed
				if string.find( self.allowed, character ) == -1: return
				
				# add it to the buffer
				self.newtext( self.text + character )
	
	def __init__( self, manager, rect, fontsize=24, maxchars=60, foreground=(0xFF, 0xFF, 0xFF, 0xFF), background=(64, 64, 64, 0xFF) ):
		rect = Rect( rect )
		self.manager = manager
		self.foreground = foreground
		self.background = background
		self.font = pygame.font.Font( None, fontsize )
		self.dirty = 1
		self.rect = Rect(rect)
		self.text = "text"
		self.scrollplace = 0
		self.cursorplace = 0
		self.maxchars = maxchars
		self.allowed = """`1234567890-=	qwertyuiop[]\\asdfghjkl;'zxcvbnm,./ ~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:"ZXCVBNM<?"""
		self.maketext()
	
	def newtext( self, newtext ):
		self.text = newtext
		self.maketext()
	
	def maketext( self ):
		# make a new surface
		self.surface = self.font.render( self.text + " ", 0, self.foreground, self.background )
		# need redraw
		self.dirty = 1
	
	def draw( self, screen ):
		# only draw if it's required
		if self.dirty == 0: return
		
		# clip our drawing
		screen.set_clip( self.rect )
		
		# clear the area
#		screen.fill( self.background, self.rect )
		screen.fill( (0xAF, 0x8F, 0x8F, 0xFF), self.rect )
		
		sw = self.surface.get_width()
		
		# text doesn't fit
		if sw > self.rect.width:
			# align the right edge
			x = self.rect.right - sw
		# text does fit
		else:
			# align the left edge
			x = self.rect.left
		
		# blit it to the screen
		screen.blit( self.surface, (x, self.rect.top) )

		# drawing is done
		self.dirty = 0

client = ClientClass( screen )
client.loop()

#clean up
pygame.time.set_timer( USEREVENT, 0 )
pygame.quit()
