# Entry.py
# Copyright (C) 2002 Alex Mercader <alex.mercader@iinet.net.au>
#
# This file is part of Curphoo.
#
# Curphoo is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Curphoo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Curphoo; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# $Id: Entry.py,v 1.4 2002/05/05 03:51:48 hacker Exp $
#
# $Log: Entry.py,v $
# Revision 1.4  2002/05/05 03:51:48  hacker
# fixed typo in process_char
#
# Revision 1.3  2002/05/05 03:46:46  hacker
# have the app print the chatter list when RETURN is hit
# without typing anything else in the input box
#
# Revision 1.2  2002/05/05 01:05:04  hacker
# modified TAB on empty input from beep to beep and print help
#
# Revision 1.1  2002/05/01 08:39:22  hacker
# Initial revision
#
#
##

import curses
from curses import ascii
#import CPtab

PMAXCOL = 1024
PMAXROW = 1
PADPOS = 1 # position entry pad on first row (from bottom) of stdscr
HISTLEN = 30

class Entry:
	"""A single row multicolumn Entry widget"""

	def __init__(self):
		self.pud = 0
		self.win = curses.newpad(PMAXROW, PMAXCOL)
		self.win.scrollok(0)
		self.win.timeout(100)
		self.win.keypad(1)

		
		self.lastx = PMAXCOL - 1				# the last column
		self.width = curses.COLS - 1			# no. visible columns
		self.leftx = 0						  # left most visible column
		self.rightx = self.leftx + self.width   # right most visible column
		self.endx = 0						   # column just right of last char
		self.x = 0							  # cursor location
		self.prevkeystroke = None
		self.y = PMAXROW - 1					# this should not change
		self.yankbuf = ''					   # some keystrokes save
												# strings here

		self.histbuf = ['']
		self.histno = 0
												# hard coded attributes
												# for noutrefresh()
												# touch at own risk
		self.sminrow = curses.LINES - PADPOS
		self.smincol = 0
		self.smaxrow = self.sminrow
		self.smaxcol = self.width

	def find_match(self, lst, word, pm_recipient='nobody'):
		if word.startswith('/'):
			w = word.lower()[1:]
			hits = []
			for e in lst:
				e[:len(w)].lower() == w and e.lower() not in \
					[e.lower() for e in hits] and hits.append(e)
			return len(hits) == 1 and '/%s' % hits.pop()

		if word.startswith('#'):
			w = word.lower()[1:]
			hits = []
			for e in lst:
				e[:len(w)].lower() == w and e.lower() not in \
					[e.lower() for e in hits] and hits.append(e)
			return '/TELL %s  ' % pm_recipient

		w = word.lower()
		hits = []
		for e in lst:
			e[:len(w)].lower() == w and e.lower() not in \
				[e.lower() for e in hits] and hits.append(e)
		return len(hits) == 1 and hits.pop()
		
	def complete(self, t, word):
		l = []
		for e in t:
			l.extend(e)
		completeword = self.find_match(l, word, t[-1][0])
		return completeword or word

	def clearpad(self):
		self.win.erase()
		self.leftx = 0
		self.rightx = self.width
		self.endx = 0
		self.x = 0

	def show(self):
		if self.x > self.rightx:
			self.rightx = self.x
			self.leftx = self.rightx - self.width
		elif self.x < self.leftx:
			self.leftx = self.x
			self.rightx = self.leftx + self.width
		self.win.noutrefresh( self.y, self.leftx,
							  self.sminrow, self.smincol,
							  self.smaxrow, self.smaxcol)

	def padtext(self):
		text = ''
		for i in range(self.endx):
			text = text + chr(ascii.ascii(self.win.inch(self.y,i)))
												# inch()  moves cursor
												# return cursor to
												# original location
		self.win.move(self.y, self.x)		   
		return text

	def process_char(self, ch, listt):
		if ch in (ascii.NL, curses.KEY_ENTER):  # CNTRL J/ENTER
			gatheredinput = self.padtext()
			#self.clearpad()
			if gatheredinput:
				self.prevkeystroke = ch
				self.histbuf.pop(0)
				if len(self.histbuf):		   # avoid duplicates
					if self.histbuf[0] != gatheredinput:
						self.histbuf.insert(0, gatheredinput)
				else:
					self.histbuf.insert(0, gatheredinput)
				self.histbuf.insert(0, '')
				self.histno = 0
				if len(self.histbuf) > HISTLEN: # save only this many commands
					self.histbuf.pop()		  # discard oldest one
				self.clearpad()
				self.show()
				return gatheredinput
			else:
				return '/users'

		elif ch == ascii.NAK:				   # CNTRL U
			if self.x:
				gatheredinput = self.padtext()
				kept_text = gatheredinput[self.x:]
				deletedtext = gatheredinput[:self.x]
				self.yankbuf = deletedtext
				self.clearpad()
				self.win.insstr(kept_text)
				self.x = 0
				self.win.move(self.y, self.x)
				#self.endx = len(s)
				self.endx = len(kept_text)
			else: curses.beep()
			self.prevkeystroke = ch

		#not functional (as yet) command history
		elif ch in (ascii.SO, curses.KEY_DOWN): # CNTRL N
			if self.histno == 0:
				curses.beep()
			else:
				hist_string = self.padtext()
				if hist_string.strip() and \
				hist_string != self.histbuf[self.histno]:
												# record changes made
												# but dont record if just
												# spaces
					self.histbuf[self.histno] = hist_string
				self.clearpad()
				self.histno = self.histno - 1
				self.win.addstr(self.histbuf[self.histno])
				self.endx = len(self.histbuf[self.histno])
				self.x = self.endx
				self.prevkeystroke = ch

		elif ch in (ascii.DLE, curses.KEY_UP): # CNTRL P
			if self.histno >= len(self.histbuf) - 1:
				curses.beep()
			else:
				hist_string = self.padtext()
				if hist_string.strip() \
				and hist_string != self.histbuf[self.histno]:
												# record changes made
												# but dont record if just
												# spaces
					self.histbuf[self.histno] = hist_string
				self.clearpad()
				self.histno = self.histno + 1
				self.win.addstr(self.histbuf[self.histno])
				self.endx = len(self.histbuf[self.histno])
				self.x = self.endx
				self.prevkeystroke = ch


												# IS THIS elif NECESSARY?
		elif ch == ascii.SP:					# inserting a space
												# ok to add character here,
												# is there enough space?
			if self.x < self.lastx and self.endx + 1 < self.lastx:
				self.win.insch(ch)
				self.x = self.x + 1			 # insch() does not move cursor,
				self.win.move(self.y, self.x)		# move it manually
				self.endx = self.endx + 1
				self.prevkeystroke = ch
			else:
				curses.beep()

		elif ascii.isprint(ch):				 # ascii.SP true here too?
			if self.x < self.lastx and self.endx + 1 < self.lastx:
				self.win.insch(ch)
				self.x = self.x + 1
				self.win.move(self.y, self.x)
				self.endx = self.endx + 1
				self.prevkeystroke = ch
			else:
				curses.beep()

		elif ch in (curses.KEY_HOME, ascii.SOH):# CNTRL A
			if self.x:
				self.x = 0
				self.win.move(self.y, self.x)
				self.prevkeystroke = ch
			else:
				curses.beep()				   # cursor already on column 0

		elif ch in (ascii.STX,curses.KEY_LEFT,ascii.BS,curses.KEY_BACKSPACE):
			if self.x > 0:
				self.x = self.x - 1
				self.win.move(0, self.x)
				if ch in (ascii.BS,curses.KEY_BACKSPACE):
					self.win.delch()
					self.endx = self.endx - 1
				self.prevkeystroke = ch
			else:
				curses.beep()				   # cursor cant go left anymore

		elif ch == ascii.EOT:				   # CNTRL D
			if self.x < self.endx:			  # cant delete what wasnt typed
				self.win.delch()				# cursor stays put
				self.endx = self.endx - 1
				self.prevkeystroke = ch
			else:
				curses.beep()

		elif ch in (curses.KEY_END, ascii.ENQ): # CNTRL E
			if self.x < self.endx:
				self.x = self.endx			  # move cursor right one column,
				self.win.move(0,self.x)		 # after last char of line
				self.prevkeystroke = ch
			else:
				curses.beep()

												# CNTRL F/KEY_RIGHT
		elif ch in (ascii.ACK, curses.KEY_RIGHT):
			if self.x < self.endx:
				self.x = self.x + 1
				self.win.move(0, self.x)
				self.prevkeystroke = ch
			else:
				curses.beep()

												# butt ugly code follows!
												# CNTRL W
												# delete word left of cursor
		elif ch == ascii.ETB:
			if not self.x:
				curses.beep()				   # nothing to delete
			else:
				deleted_word = ''
				while self.x > 0:
					self.x = self.x - 1		 # inspect char left of cursor
					self.win.move(self.y, self.x)
					deleted_char = chr(ascii.ascii(self.win.inch()))
					if deleted_char == ' ':
						self.win.delch()
						deleted_word = deleted_char+deleted_word 
						self.endx = self.endx - 1
												# deleting contigous spaces to
												# the left of cursor
						continue
					else:
						self.win.delch()
						#buf += c
						deleted_word =  deleted_char+deleted_word 
						self.endx = self.endx - 1
						while self.x > 0:
							self.x = self.x - 1
							self.win.move(self.y, self.x)
							deleted_char = chr(ascii.ascii(self.win.inch()))
							if deleted_char != ' ':
								self.win.delch()
								deleted_word =  deleted_char+deleted_word 
								self.endx = self.endx - 1
							else:
												# move cursor back right one col
								self.x = self.x + 1
								self.win.move(self.y, self.x)
								break
						break
				if self.prevkeystroke == ascii.ETB:
					self.yankbuf = deleted_word + self.yankbuf 
				else:
					self.yankbuf = deleted_word
				self.prevkeystroke = ch

												# the autocomplete mechanism
		elif ch == ascii.TAB:
			if not self.x:
				curses.beep()
				return '/help'
			else:
				wholetext = self.padtext()
				textleft = wholetext[:self.x]
				textright = wholetext[self.x:]
				if textleft[self.x-1] == ' ':
					curses.beep()
				else:
												# need to know where this
												# partial word begins
					i = textleft.rfind(' ')
					if i != -1:
						word = textleft.lstrip().split(' ').pop()
						newword = self.complete(listt, word)
						if newword != word:	 # if tab complete did something
							nextrachars = len(newword) - len(word)
												# will it fit?
							if self.endx + nextrachars < self.lastx:
								wholetext = "%s %s%s" % (
											textleft[:i], newword, textright)
								oldx = self.x
								oldendx = self.endx
								self.clearpad()
								self.win.insstr(wholetext)
								self.x = oldx + nextrachars
								self.win.move(self.y, self.x)
								self.endx = oldendx + nextrachars
							else:
								curses.beep()
						else:
							curses.beep()
					else:
						word = textleft
						newword = self.complete(listt, word)
						if newword != word:	 # if tab complete did something
							nextrachars = len(newword) - len(word)
												# will it fit?
							if self.endx + nextrachars < self.lastx:
								wholetext = "%s%s" % (newword, textright)
								oldx = self.x
								oldendx = self.endx
								self.clearpad()
								self.win.insstr(wholetext)
								self.x = oldx + nextrachars
								self.win.move(self.y, self.x)
								self.endx = oldendx + nextrachars
							else:
								curses.beep()
						else:
							curses.beep()
				self.prevkeystroke = ch

		elif ch == ascii.VT:					# CNTRL K
			if self.x < self.endx:
				self.yankbuf = ''
				for i in range(self.x, self.endx):
					self.yankbuf = self.yankbuf + \
					chr(ascii.ascii(self.win.inch(self.y,i)))

												# the inch operation moves
												# the cursor,  move cursor
												# back where it was
				self.win.move(self.y, self.x)
				self.win.clrtoeol()
				self.endx = self.x
				self.prevkeystroke = ch
			else:
				curses.beep()

		elif ch == ascii.EM:					# CNTRL Y
			if self.yankbuf:
				if len(self.yankbuf) + self.endx < self.lastx:
					self.win.insstr(self.yankbuf)
					self.endx = self.endx + len(self.yankbuf)
					self.x = self.x + len(self.yankbuf)
					self.win.move(self.y, self.x)
					self.prevkeystroke = ch
				else:
					curses.beep()
			else:
				curses.beep()

		self.show()
	
	def pp(self):
		return self.pud

	def edit(self, *listt):
		self.show()
		ch = self.win.getch()
		if ch == curses.KEY_NPAGE:
			self.pud = -1
		elif ch == curses.KEY_PPAGE:
			self.pud = 1
		elif ch == ascii.ETX:
			self.pud = 9999
		elif ch == ascii.FF:
			self.pud = 999
		else:
			self.pud = 0
		return self.process_char(ch, listt)
