import runtime
import widget
import uiconfig
from cursor import Cursor
import status
import utils
import config

def pop(list):
	try:
		return list.pop()
	except IndexError:
		return None

SI = chr(15)
SO = chr(14)
TAG_TEXT = '%stext%s' % (SI, SO)
TAG_IMAGE = '%simg%s' % (SI, SO)
TAG_NEWLINE = '%snl%s' % (SI, SO)

class RichTextOverflowError(Exception):
	pass

class RichTextObject(object):
	def __init__(self, parent):
		self._parent = parent
		self._widget = None
		self.dirty = True
		self.private = None
		self.geom_cache = 0,0,0,0
		self.hiddenset = 0

	def set_private_data(self, d):
		self.private = d
	def get_private_data(self):
		return self.private
	def get_data(self):
		return (self.tag(), self.get_text(), self.get_private_data())

	def free(self):
		self.hide()
		self._parent = None
		if self._widget:
			self._widget.free()

	def show(self):
		if self.hiddenset == 1:
			'''
			if self._widget:
				ShowWidget = self._widget
				t = unicode('')
				if len(ShowWidget.text) > 0:
					i = 0
					while( i < (len(ShowWidget.text)-1) ):
						t += unicode('*')
						i += 1
					t += ShowWidget.text[-1:]
				ShowWidget.text = t
				ShowWidget.show()
			'''
			if self._widget:
				print 'yaaaa:self._widget.text=', self._widget.text
				if status.RichTextFocus != 1:
					if status.prev_tc != None:
						status.prev_tc.hide()
						status.prev_tc.free()
						status.prev_tc = None
					if status.prev_tc_box != None:
						status.prev_tc_box.hide()
						status.prev_tc_box.free()
						status.prev_tc_box = None
				if self._widget.text[-1:] != '*' and status.RichTextFocus == 1:
					if status.prev_tc != None:
						status.prev_tc.hide()
						status.prev_tc.free()
						status.prev_tc = None
					if status.prev_tc_box != None:
						status.prev_tc_box.hide()
						status.prev_tc_box.free()
						status.prev_tc_box = None
					if status.crnt_key != config.CLEAR:
						tc_box = utils.put_image('/usr/local/lgvp/images/pwchar.png',(143, self._widget.pos[1]-2))
						tc_box.show()
						status.prev_tc_box = tc_box
						tc = runtime.evas.text()
						tc.font=self._parent._text_font
						tc.color=self._parent._text_color
						tc.text= self._widget.text[-1:]
						tc.pos =(151, self._widget.pos[1])
						tc.show()
						status.prev_tc = tc
				
				ShowWidget = self._widget
				t = unicode('')
				if len(ShowWidget.text) > 0:
					i = 0
					while( i < len(ShowWidget.text) ):
						t += unicode('*')
						i += 1
				ShowWidget.text = t
				ShowWidget.show()
				'''
				CharWidget = self._widget
				print 'self._widget.pos=', self._widget.pos
				CharWidget.pos = (self._widget.pos[0] - 30, self._widget.pos[1])
				print 'CharWidget.pos =', CharWidget.pos 
				#CharWidget.text = self._widget.text[-1:]
				CharWidget.text = '1234567890'
				CharWidget.show()
				'''

				
		else:
			if self._widget: self._widget.show()
		
	def hide(self):
		if self._widget: self._widget.hide()
	def x(self):
		return self.geom_cache[0]
	def y(self):
		return self.geom_cache[1]
	def width(self, length=-1):
		return self.geom_cache[2]
	def height(self):
		return self.geom_cache[3]
	def move(self, x, y):
		if self._widget:
			self._widget.move(x,y)
			self.geom_cache = self._widget.geometry_get()

	def dup(self):
		pass
	def set_text(self, data):
		pass
	def get_text(self):
		pass
	def render(self):
		pass
	def set_clip(self, clip):
		if self._widget: self._widget.clip_set(clip)

	# return newly created object or False
	def split(self, width, force = False):
		return False

	def split_in_length(self, len):
		return False

	# return True or False
	def join(self, o):
		return False

	def message_length(self):
		return self.length()

	def length(self):
		return 1

	def cut(self, index, length):
		return False

class RichTextObject_NewLine(RichTextObject):
	def __init__(self, parent):
		RichTextObject.__init__(self, parent)
		self._ul = None

	def tag(self):
		return TAG_NEWLINE
	def move(self, x, y):
		self.geom_cache = (x,y,0,16)

	def get_text(self):
		return '\n'

class RichTextObject_Text(RichTextObject):
	def __init__(self, parent, hiddenset = 0):
		RichTextObject.__init__(self, parent)
		self.dirty = True
		self._widget = runtime.evas.text()
		self._widget.font = parent._text_font
		self._widget.color = parent._text_color
		self._descent = self._widget.descent_get()
		self._ul = None
		self._add_word_rect = None
		self._text = unicode('')
		self.hiddenset = hiddenset

	def tag(self):
		return TAG_TEXT

	def move(self, x,y ):
		RichTextObject.move(self,x,y)
		if self._ul:
			self.update_ul()

	def free(self):
		if self._ul: self._ul.free()
		if self._add_word_rect: self._add_word_rect.free()
		RichTextObject.free(self)

	def hide(self):
		if self._ul: self._ul.hide()
		if self._add_word_rect: self._add_word_rect.hide()
		RichTextObject.hide(self)

	def show(self):
		self.update_ul()
		RichTextObject.show(self)

	def enable_ul(self, ul):
		if (self._ul and ul) or (not self._ul and not ul):
			return

		if ul:
			if not self._ul:
				self._ul = runtime.evas.rectangle()
				self._ul.color = uiconfig.edit_underline_color
		else:
			if self._ul:
				self._ul.free()
				self._ul = None
				if status.password_style:
					self._parent.formatting()

	def update_ul(self):
		if not self._ul:
			return

		x1 = self.x()
		x2 = self.x() + self.width()
		y1 = self.y()
		y2 = y1 + self.height()

		if self._parent._ul_shape == 'rectangle':
			self._widget.color = uiconfig.edit_invert_color
			self._widget.layer_set(100)

			if not self._add_word_rect:
				self._add_word_rect = runtime.evas.rectangle()
			self._add_word_rect.layer_set(99)
			self._add_word_rect.color = uiconfig.edit_underline_rect_color
			self._add_word_rect.move(x1, y1)
			self._add_word_rect.resize(x2-x1, y2 - y1)
			self._add_word_rect.show()

			self._ul.color = uiconfig.edit_underline_invert_color
			self._ul.move(x1, y2 - self._descent)
			self._ul.resize(x2-x1, 1)
			self._ul.layer_set(100)

		elif self._parent._ul_shape == 'line':
			self._widget.color = uiconfig.edit_color

			if self._add_word_rect:
				self._add_word_rect.free()
				self._add_word_rect = None

			self._ul.color = uiconfig.edit_underline_color
			self._ul.move(x1, y2 - self._descent)
			self._ul.resize(x2-x1, 1)
		else:
			pass
			#print 'Unknown shape [%s]' % self._parent._ul_shape
			#assert False

		if self._parent.clip:
			self._ul.clip_set(self._parent.clip)

		# KA: [20070831] hangul lvp-2000
		#self._ul.show()

	def set_text(self, data):
		self._text = data
		self._widget.text_set(self._text)
		self.geom_cache = self._widget.geometry_get()
		self.dirty = True

	def get_text(self):
		return self._text

	def get_last_character(self):
		return self._text[-1:]
		
	def width(self, length=-1):
		if length < 0:
			return self._widget.horiz_advance_get()
		elif length == 0:
			return 0
		else:
			t = runtime.evas.text()
			t.font = self._widget.font_get()
			t.text_set(self._text[:length])
			width = t.horiz_advance_get()
			t.free()
			return width

	def dup(self):
		o = RichTextObject_Text(self._parent)
		o._widget.text_set(self._text)
		name, size = self._widget.font_get()
		o._widget.font_set(name,size)
		r,g,b,a = self._widget.color_get()
		o._widget.color_set(r,g,b,a)
		clip = self._widget.clip_get()
		if clip: o._widget.clip_set(clip)
		if self._ul: o.enable_ul(True)
		o._widget.layer_set(self._widget.layer_get())
		o.move(self.x(), self.y())

		return o

	# force
	#  * True: split in byte
	#  * False: split in word
	def split(self, base_x, width, force = False):
		if width == 0:
			width = base_x
		#assert width > 0
		rc = self._widget.char_coords_get(width, 0)
		split_pos = rc[0]
		if split_pos == 0:
			return None

		if split_pos < 0 or split_pos >= len(self._text):
			# ±ÛÀÚÆø °è»êÀ» horiz_advance_get()À¸·Î ÇÏ±â ¶§¹®¿¡
			# ½ÇÁ¦ ±ÛÀÚ°¡ ¾ø´Â À§Ä¡¿¡¼­µµ ÀÚ¸¦·Á°í ÇÒ ¶§°¡ ÀÖ´Ù.
			# work-around·Î ¸¶Áö¸· ±ÛÀÚ¿¡ À§Ä¡ÇÏµµ·Ï ¼öÁ¤
			split_pos = len(self._text) - 1

		if not force and split_pos > 0:
			if self._text[split_pos-1] != ' ':
				split_pos = self._text[:split_pos].rfind(' ')
				if split_pos <= 0:
					# can't split
					return None

				# the space will be included in the first one that is splitted
				split_pos += 1

		text = self._text
		self.set_text(self._text[:split_pos])
		self.geom_cache = self._widget.geometry_get()

		while self.width() >= width and split_pos > 0:
			split_pos -= 1
			self.set_text(text[:split_pos])
			self.geom_cache = self._widget.geometry_get()

		o = self.dup()
		o.set_text(text[split_pos:])
		self.dirty = True
		return o

	#Áß°£¿¡ °ªÀ» ÀÔ·ÂÇÏ¸é ±×°÷¿¡ µ¥ÀÌÅ¸¸¦ »ðÀÔÇÏ±â À§ÇÏ¿© ±× Ä¿¼­ À§Ä¡ÀÇ ¾ÕÀÇ text¸¦ ¹Ù²Ù°í µÚÀÇ ±ÛÀÚ´Â »õ·Î¿î ¿ÀºêÁ§Æ®·Î µî·ÏÇÑ´Ù
	#¸¸¾à µÞ±ÛÀÚ°¡ ¾øÀ¸¸é ¾Æ¹«·± µ¿ÀÛÀ» ÇÏÁö ¾Ê´Â´Ù
	def split_in_length(self, length):
		#assert length > 0
		#assert length < len(self._text)

		t1 = self._text[:length]
		t2 = self._text[length:]

		if not t2:
			return None

		self.set_text(t1)
		o = self.dup()
		o.set_text(t2)
		self.dirty = True
		return o

	def join(self, o):		#Çö text¿Í ÀÎÀÚ·Î µé¾î¿Â ¿ÀºêÁ§Æ®ÀÇ text¸¦ Çö ¿ÀºêÁ§Æ®ÀÇ text¿¡ +ÇÔ
		if o.tag() != TAG_TEXT:
			return False

		if (self._ul and not o._ul) or (not self._ul and o._ul):
			return False

		if not self.dirty and not o.dirty and self.y() == o.y():
			self.dirty = False
		else:
			self.dirty = True

		l = len(self._text)
		self.set_text( self._text + o.get_text() )
		self.dirty = True
		return True

	def message_length_KT(self):
		if self._ul and self._parent._ul_shape == 'rectangle':
			return 0
		else:
			return self.length_KT()

	def message_length(self):
		if self._ul and self._parent._ul_shape == 'rectangle':
			return 0
		else:
			return self.length()

	def length(self):
		return len(self._text)

	def length_KT(self):
		# shchun : in case non euc-kr
		#return len(self._text.encode('euc-kr'))
		try:
			ret = len(self._text.encode('euc-kr'))
		except:
			ret = len(self._text)
		return ret
		# end shchun
	def cut(self, index, length):
		#assert index >= 0
		#assert index + length <= self.length()

		text = self._text[:index] + self._text[index+length:]
		self.set_text(text)
		self.dirty = True

class RichTextObject_Image(RichTextObject):
	def __init__(self, parent):
		RichTextObject.__init__(self, parent)
		self.dirty = True
		self._widget = runtime.evas.image()
		self._ul = None

	def tag(self):
		return TAG_IMAGE

	def move(self, x, y):
		self._widget.move(x+2,y)
		width,height = self._widget.geometry_get()[2:]
		self.geom_cache = (x, y, width + 2*2, height)

	def set_text(self, data):
		self._widget.file_set(data.encode('utf-8'))
		self._widget.smooth_scale_set(0)
		width, height = self._widget.size_get()
		self._widget.fill_set(0,0,width,height)
		self._widget.resize(width,height)
		x,y,width,height = self._widget.geometry_get()
		self.geom_cache = (x, y, width + 2*2, height)

	def get_text(self):
		return unicode(self._widget.file_get(), 'utf-8')

	def message_length_KT(self):
		return self.message_length()
		
	def message_length(self):
		p = self.private
		if p[0] == 'emoticon':
			return len(p[1])
		elif p[0] == 'picture':
			import utils
			return utils.get_file_size(self._widget.file_get())
		elif p[0] == 'audio':
			import utils
			return utils.get_file_size(p[1])
		else:
			pass
			#print 'p1 = [%s]' % p1
			#assert False

##########################################################################
class RichTextBase(widget.Widget):
	def __init__(self, pos, size, edit=False):
		widget.Widget.__init__(self)

		self.x = pos[0]
		self.y = pos[1]
		self.width = size[0]
		self.height = size[1]

		self.objs = []

		self.clip = None

		self._edit = edit
		self.cursor = None
		self.cursor_hidden = False
		self._max = -1
		self.hiddenset = 0
		self._ul_shape = 'line'

		if self._edit:
			self.cursor = Cursor(self)
			self.cursor_idx = 0
			self.cursor_offset_in_obj = 0
			self.cursor.queue_update()

	def free(self):
		widget.Widget.free(self)

		for o in self.objs:
			o.free()

		if self.cursor:
			self.cursor.free()

	def cursor_end_pos(self):
		idx = self.cursor_idx
		off = self.cursor_offset_in_obj
		
		self.cursor_idx = len(self.objs)
		self.cursor_offset_in_obj = 0
		
		max_cursor_x, max_cursor_y,h = self.cursor_pos_height()

		self.cursor_idx = idx
		self.cursor_offset_in_obj = off

		my_cursor_x, my_cursor_y,h = self.cursor_pos_height()
		if my_cursor_x == max_cursor_x:
			return 1
		else:
			return 0
		#print "[yylee debug] ",max_cursor_x, my_cursor_x
		#print "[yylee debug] cursor_idx:%d, cursor_offset_in_obj=%d" % (self.cursor_idx, self.cursor_offset_in_obj)
		#return self.cursor_idx

	def set_max(self, max):
		self._max = max

	def length(self):
		l = 0
		for o in self.objs:
			l += o.length()

		return l

	def check_internal(self):
		return

		if not self._edit: return

		#assert self.cursor_offset_in_obj >= 0
		#if self.cursor_offset_in_obj != 0:
	 	#	assert self.cursor_idx < len(self.objs)
		#	assert self.cursor_offset_in_obj < self.objs[self.cursor_idx].length()
		#else:
		#	assert self.cursor_idx <= len(self.objs)

	def show(self):
		self.hidden = False
		self.formatting()
		if self.cursor and not self.cursor_hidden: self.cursor.show()

	def hide(self):
		for o in self.objs:
			o.hide()
		if self.cursor: self.cursor.hide()
		self.hidden = True

	def move(self, x, y):
		dx = x - self.x
		dy = y - self.y
		self.x = x
		self.y = y

		for o in self.objs:
			o.move( o.x() + dx, o.y() + dy )
		runtime.evas.render()

	def get_text(self):
		t = unicode('')
		try:
			for o in self.objs:
				t += o.get_text()
		except:
			pass

		#assert isinstance(t, unicode)
		return t.encode('utf-8')		#À¯´ÏÄÚµå¸¦ utf-8·Î º¯È¯

	def object_factory(self, tag = TAG_TEXT):
		if tag == TAG_IMAGE:
			return RichTextObject_Image(self)
		elif tag == TAG_TEXT:
			return RichTextObject_Text(self, hiddenset = self.hiddenset)
		elif tag == TAG_NEWLINE:
			o = RichTextObject_NewLine(self)
			o.set_private_data(uiconfig.richtext_min_line_height)
			return o
		else:
			pass
			#print 'Unknown tag name [%s]' % tag
			#assert False

	def object_parser(self, text):
		objs = []
		o = None
		data = ''
		tag_name = ''

		for c in text:
			if c == '\r':
				continue

			if c == '\n':
				if o:
					#assert o.tag() == TAG_TEXT
					o.set_text(data)
					objs.append(o)
					data = ''
					o = None

				objs.append(self.object_factory(TAG_NEWLINE))
				continue
			elif c == SI:
				if o:
					o.set_text(data)
					objs.append(o)
					data = ''
					o = None
				tag_name = c
			elif c == SO:
				tag_name += c
				o = self.object_factory(tag_name)
				tag_name = ''
			else:
				if tag_name:
					tag_name += c
				else:
					if not o:
						o = self.object_factory()
					data += c

		if o:
			o.set_text(data)
			objs.append(o)
			o = None

		return objs

	def handle_join(self, o, o2):
		if self._edit:
			coff = self.cursor_offset_in_obj
			cidx = self.cursor_idx

			i2 = self.objs.index(o2)

			if i2 == cidx:
				cidx -= 1
				coff = o.length() - o2.length() + coff

			elif cidx > i2:
				cidx -= 1

			self.cursor_offset_in_obj = coff
			self.cursor_idx = cidx

		self.objs.remove(o2)
		o2.free()

	def handle_split(self, o, o2):
		i = self.objs.index(o)
		self.objs.insert(i+1, o2);

		if self._edit:
			coff = self.cursor_offset_in_obj
			cidx = self.cursor_idx
			if i == cidx:
				if coff >= o.length():
					cidx += 1
					coff -= o.length()

			elif i < cidx:
				cidx += 1

			self.cursor_offset_in_obj = coff
			self.cursor_idx = cidx

	def set_text(self, text):
		#assert not isinstance(text, unicode)
		try:		
			text = unicode(text, 'utf-8')		
		except:		
			text = ''

		for o in self.objs:
			o.free()
		self.objs = []
		self.objs = self.object_parser(text)

		if self.objs:
			self.formatting()
		if self._edit: self.cursor_end()		

	def insert_objs(self, nobjs):
		#print "[yylee debug] _max:%d, length:%d"%(self._max, self.message_length_KT())
		#assert self._edit
		'''
		if self._max > 0:
			left = self._max - self.message_length()
			tmpobjs = nobjs
			nobjs = []
			for o in tmpobjs:
				if o.message_length() <= left:
					left -= o.message_length()
					nobjs.append(o)
				else:
					o.free()
		'''
		if self._max > 0:
			left = self._max - self.message_length_KT()
			tmpobjs = nobjs
			nobjs = []
			for o in tmpobjs:
				if o.message_length_KT() <= left:
					left -= o.message_length_KT()
					nobjs.append(o)
				else:
					o.free()

		idx = self.cursor_idx
		off = self.cursor_offset_in_obj

		o2 = None
		if off > 0:
			o2 = self.objs[idx].split_in_length(off)
			#assert o2
			idx += 1
			off = 0

		for o in nobjs:
			if idx > 0:
				last_o = self.objs[idx-1]
				o.move(last_o.x()+last_o.width(), last_o.y())
			o.dirty = True
			self.objs.insert(idx, o)		#ÀüÃ¼ ¿ÀºêÁ§Æ® °ü¸® objs¿¡ »ðÀÔ
			idx += 1

		if o2:
			self.objs.insert(idx, o2)

		#assert off == 0

		# just for assertion
		#if idx == len(self.objs): assert off == 0
		#assert idx <= len(self.objs)
		#if idx < len(self.objs): assert off < self.objs[idx].length()

		self.cursor_idx = idx
		self.cursor_offset_in_obj = off

		self.check_internal()
		self.formatting()
		self.check_internal()

		if not self.hidden:
			# in showed only. see widget.py
			self.cursor.queue_update()

	def insert_text(self, text, ul = False):
		#assert not isinstance(text, unicode)
		self.insert_unicode_text(unicode(text, 'utf-8'))

	def set_edit_buf(self, text):
		first = True
		for o in self.objs:
			if o._ul:
				if first:
					o.set_text(text)
					o.update_ul()
					first = False
				else:
					self.objs.remove(o)
					self.cursor_idx -= 1

		self.formatting()

	def insert_unicode_text(self, text, ul=False):
		nobjs = self.object_parser(text)
		for o in nobjs:
			if o.tag() == TAG_TEXT:
				o.enable_ul(ul)

		self.insert_objs(nobjs)

	def can_be_inserted(self, o):
		if self._max > 0:
			left = self._max - self.message_length()
			if o.message_length() > left:
				return False

		return True

	def insert_image(self, filename, data = None):
		o = self.object_factory(TAG_IMAGE)
		o.set_text(filename)
		o.set_private_data(data)

		if not self.can_be_inserted(o):
			o.free()
			raise RichTextOverflowError, ''

		self.insert_objs((o,))

	def insert_audio(self, filename, data = None):
		o = self.object_factory(TAG_IMAGE)
		o.set_text(uiconfig.image_dir + uiconfig.edit_audio_icon)
		o.set_private_data(('audio', filename, data))

		if not self.can_be_inserted(o):
			o.free()
			raise RichTextOverflowError, ''

		self.insert_objs((o,))

	def get_data(self):
		data = []
		for o in self.objs:
			data.append(o.get_data())

		return data

	def get_length(self):
		l = 0
		for o in self.objs:
			l += o.length()

		return l

	def message_length_KT(self):		#Áö±Ý±îÁö ÀÔ·ÂµÈ char(¿ÀºêÁ§Æ®)ÀÇ ¼ö¸¦ ¸®ÅÏÇÑ´Ù.
		l = 0
		for o in self.objs:
			l += o.message_length_KT()

		return l

	def message_length(self):		#Áö±Ý±îÁö ÀÔ·ÂµÈ char(¿ÀºêÁ§Æ®)ÀÇ ¼ö¸¦ ¸®ÅÏÇÑ´Ù.
		l = 0
		for o in self.objs:
			l += o.message_length()

		return l

	def set_max(self, max):
		self._max = max

	def set_clip(self, clip):
		self.clip = clip
		for o in self.objs:
			o.set_clip(clip)

	def backspace(self, count=1):
		#assert self._edit

		idx = self.cursor_idx
		off = self.cursor_offset_in_obj

		for i in range(idx, len(self.objs)):
			self.objs[i].dirty = True

		while count > 0:
			if self.cursor_offset_in_obj == 0:
				if idx == 0:
					return False

				o = self.objs[idx-1]
				if o.length() <= count:
					count -= o.length()

					idx = self.objs.index(o)
					off = 0

					o.free()
					self.objs.remove(o)

					# RHC / [20060809_1]
					# SMS Write ½Ã¿¡ ÁÙ¹Ù²Þ¿¡ ¹®Á¦°¡ ÀÖ´Ù.
					try:
						self.objs[idx-1].dirty = True
					except IndexError:
						#roxia_trace('IndexError')
						pass
					# RHC / [20060809_1]--
				else:
					o.cut(o.length()-count, count)
					idx = self.objs.index(o) + 1
					off = 0
					count = 0
			else:
				o = self.objs[idx]
				#assert off < o.length()

				if off <= count:
					count -= off
					o.cut(0, off)
					idx = self.objs.index(o)
					off = 0
				else:
					off -= count
					o.cut(off, count)
					idx = self.objs.index(o)
					count = 0

		self.cursor_idx = idx
		self.cursor_offset_in_obj = off

		self.check_internal()
		self.formatting()
		self.check_internal()
		self.cursor.queue_update()

	def cursor_pos_height(self):
		idx = self.cursor_idx
		off = self.cursor_offset_in_obj

		if off == 0:
			if idx == 0:
				return self.x, self.y, uiconfig.richtext_min_line_height
			else:
				#assert idx <= len(self.objs)
				if idx >= len(self.objs):
					o = self.objs[idx-1]
					if o.tag() == TAG_NEWLINE:
						return self.x, (o.y() + o.get_private_data()), uiconfig.richtext_min_line_height
					else:
						return (o.x() + o.width() - 2), o.y(), o.height()
				else:
					o = self.objs[idx]
					return o.x(), o.y(), o.height()
		else:
			o = self.objs[idx]
			return (o.x() + o.width(off) - 2), o.y(), o.height() # 1 is just for tuning

	def cursor_backward(self):
		#assert self._edit
		if self.cursor_offset_in_obj == 0:
			if self.cursor_idx == 0:
				return False
			self.cursor_idx -= 1
			self.cursor_offset_in_obj = self.objs[self.cursor_idx].length() - 1
		else:
			self.cursor_offset_in_obj -= 1

		self.formatting()
		self.cursor.queue_update()
		return True

	def cursor_forward(self):
		#assert self._edit
		if self.cursor_idx == len(self.objs):
			return True

		isul = False
		for o in self.objs:
			if o._ul:
				isul = True
				break
		if isul:
			noff = self.cursor_offset_in_obj
		else:
			noff = self.cursor_offset_in_obj + 1
			if noff == self.objs[self.cursor_idx].length():
				noff = 0
				self.cursor_idx += 1

		self.cursor_offset_in_obj = noff

		self.formatting()
		self.cursor.queue_update()
		return True

	def cursor_up(self):
		return False

	def cursor_down(self):
		return False

	def cursor_start(self):
		self.cursor_idx = 0
		self.cursor_offset_in_obj = 0
		self.cursor.queue_update()

	def cursor_end(self):
		self.cursor_idx = len(self.objs)
		self.cursor_offset_in_obj = 0
		self.cursor.queue_update()

	def cursor_adj_ch(self):
		if self.cursor_offset_in_obj == 0:
			if self.cursor_idx == 0:
				return False
			else:
				idx = self.cursor_idx-1
				off = self.objs[idx].length()-1
		else:
			idx = self.cursor_idx
			off = self.cursor_offset_in_obj - 1

		if self.objs[idx].tag() == TAG_TEXT:
			t = self.objs[idx].get_text()
			return t[off]
		return False

	def set_ul_shape(self, shape):
		#assert shape in ('line', 'rectangle')
		self._ul_shape = shape

	def is_start_of_sentance(self):
		idx = self.cursor_idx
		off = self.cursor_offset_in_obj

		if off > 0:
			o = self.objs[idx]
			for j in range(off-1, -1, -1):
				c = o.get_text()[j]
				if c in (' ', '\t'):
					continue
				if c in ('.', '?', '!'):
					return True
				else:
					return False

		if idx == 0:
			return True

		for i in range(idx-1, -1, -1):
			o = self.objs[i]
			if o.tag() != TAG_TEXT:
				return True

			for j in range(o.length()-1, -1, -1):
				c = o.get_text()[j]
				if c in (' ', '\t'):
					continue
				if c in ('.', '?', '!'):
					return True
				else:
					return False

		return True

	def full(self):
		if self._max > 0 and self.message_length_KT() >= self._max:
			return True
		return False

	def cursor_show(self):
		self.cursor_hidden = False
		if self.cursor: self.cursor.show()

	def cursor_hide(self):
		self.cursor_hidden = True
		if self.cursor: self.cursor.hide()

########################################################################
class RichText(RichTextBase):
	def __init__(self, pos=(0,0), size=(200,200), edit=False, text_font=uiconfig.richtext_font, text_color=uiconfig.richtext_color):
		RichTextBase.__init__(self, pos, size, edit)

		self._format_delayed = False
		self._text_font = text_font
		self._text_color = text_color

	def get_height(self):
		y1 = self.y
		y2 = y1

		if not self.objs:
			return 0

		last_y = self.objs[-1].y()

		for i in range(len(self.objs)-1, -1, -1):
			o = self.objs[i]
			if o.y() < last_y:
				break

			if y2 < o.y() + o.height():
				y2 = o.y() + o.height()

		return y2 - y1

	def formatting(self):
		if self.hidden:
			return

		if self._format_delayed:
			return

		base_x = self.x	#edit pos
		base_y = self.y
		x2 = base_x + self.width

		#clipping area
		if self.clip:
			cx1, cy1, cw, ch = self.clip.geometry_get()
			cx2 = cx1 + cw
			cy2 = cy1 + ch
		else:
			cx1 = base_x
			cy1 = base_y
			cx2 = x2
			cy2 = base_x + self.height

		#editor¿¡ µé¾î°¡´Â ¹®ÀÚ ¶óÀÎ¼ö
		base_line_height = uiconfig.richtext_min_line_height

		ypos = base_y
		xpos = base_x
		line_height = base_line_height
		force = False

		# join objs in same line
		objs = self.objs[:]
		objs.reverse()
		while objs:		#°°Àº ¶óÀÎÀÇ text¸¦ ÇÏ³ªÀÇ ¿ÀºêÁ§Æ®·Î ¸¸µë
			o = pop(objs)
			if o.tag() != TAG_TEXT:
				continue

			o2 = pop(objs)

			while o2 and o2.y() == o.y() and o.join(o2):
				self.handle_join(o, o2)
				o2 = pop(objs)

			if o2: objs.append(o2)

		objs = self.objs[:]
		objs.reverse()

		# skip un-touched objs
		count = 0
		while objs:
			o = pop(objs)
			if o.dirty:
				objs.append(o)
				break

			o.show()
			count += 1

		if objs:
			o = objs[-1]
			last_clean_obj_idx = self.objs.index(o)-1
			if last_clean_obj_idx >= 0:
				o = self.objs[last_clean_obj_idx]
				if o.tag() == TAG_NEWLINE:
					xpos = base_x
					ypos = o.y() + o.get_private_data()
					line_height = base_line_height
				else:
					xpos = o.x() + o.width()
					ypos = o.y()
					line_height = base_line_height
					if o.height() > line_height:
						line_height = o.height()

					for i in range(last_clean_obj_idx-1, -1, -1):
						o = self.objs[i]
						if o.y() < ypos:
							break;

						if o.height() > line_height:
							line_height = o.height()

		# join
		nobjs = []
		count = 0
		while objs:
			o = pop(objs)
			o2 = pop(objs)
			while o2 and o.join(o2):
				count += 1
				self.handle_join(o, o2)
				o2 = pop(objs)

			if o2: objs.append(o2)
			nobjs.append(o)

		objs = nobjs
		objs.reverse()


		# start to layout from the first dirty object
		last_o = None
		while objs:
			o = pop(objs)
			if o.tag() == TAG_NEWLINE:
				o.move(xpos, ypos)
				o.show()
				o.set_private_data(line_height)
				o.dirty = False

				ypos += line_height
				xpos = base_x
				line_height = base_line_height

			elif o.tag() == TAG_IMAGE:
				if xpos + o.width() <= x2:
					o.move(xpos, ypos)
					o.show()
					o.dirty = False
					if line_height < o.height(): line_height = o.height()
					xpos += o.width()
				else:
					# start in new line
					objs.append(o)
					xpos = x2

			elif o.tag() == TAG_TEXT:
				if xpos + o.width() <= x2:
					o.move(xpos, ypos)
					o.show()
					o.dirty = False
					if line_height < o.height(): line_height = o.height()
					xpos += o.width()
				else:
					if o._ul:
						o2 = o.split(base_x, x2-xpos, True)
					elif last_o and last_o.tag() == TAG_TEXT and last_o._ul:
						o2 = o.split(base_x, x2-xpos, False)
						if not o2:
							o2 = o.split(base_x, x2-xpos, True)
					else:
						o2 = o.split(base_x, x2-xpos, False)

					if not o2:
						if xpos == base_x:
							o2 = o.split(base_x, x2-xpos, True)
					if o2:
						o.move(xpos, ypos)
						o.show()
						o.dirty = False
						if line_height < o.height(): line_height = o.height()

						self.handle_split(o, o2)
						objs.append(o2)
					else:
						objs.append(o)

					# start in new line
					xpos = x2
			else:
				pass
				#print 'Unknown tag [%s]' % o.tag()
				#assert False

			if xpos == x2:
				# 20: formatting margin
				# cursor_downÀ» ÇÒ¶§ formattingÇÏ±â Àü¿¡ cursor_downÀ»
				# ¼öÇàÇÏ°í formattingÀ» ÇÏ±â ¶§¹®¿¡ formattingÇÒ¶§ ÇÑÁÙ
				# Á¤µµÀÇ marginÀ» ÁÖÁö ¾ÊÀ¸¸é ÀÌ»óÇÑ°÷À¸·Î cursor°¡
				# ÀÌµ¿ÇÏ´Â °æ¿ì°¡ »ý±æ ¼ö ÀÖ´Ù.
				if ypos >= cy2 + 20:
					if self._edit:
						if self.objs.index(o) > self.cursor_idx:
							break;

				ypos += line_height
				xpos = base_x
				line_height = base_line_height

			#assert xpos < x2
			last_o = o
			force = False

		# hiding remain objects
		while objs: objs.pop().hide()

		self.set_clip(self.clip)

	def cursor_up(self):
		#assert self._edit
		if self.cursor_idx == 0 and self.cursor_offset_in_obj == 0:
			return False

		cur_x, cur_y = self.cursor_pos_height()[:2]

		i = self.cursor_idx - 1

		# skip objects in the same line
		while i >= 0 and self.objs[i].y() >= cur_y:
			i -= 1

		# find object in cur_x
		while i >= 0:
			if self.objs[i].x() <= cur_x:
				break;

			i -= 1

		if i < 0:
			# not found
			self.cursor_idx = 0
			self.cursor_offset_in_obj = 0
		else:
			self.cursor_idx = i
			o = self.objs[i]
			for off in range(o.length()):
				off += 1
				if o.x() + o.width(off) >= cur_x:
					if off > 0:
						if abs(o.x() + o.width(off) - cur_x) > abs(o.x() + o.width(off-1) - cur_x):
							off -= 1
					break;

			#assert off <= o.length()
			if off == o.length():
				off -= 1
			self.cursor_idx = i
			self.cursor_offset_in_obj = off

		self.formatting()
		self.cursor.queue_update()
		return True

	def cursor_down(self):
		#assert self._edit
		#assert self.cursor_idx <= len(self.objs)

		if self.cursor_idx == len(self.objs):
			return False

		idx = self.cursor_idx
		off = self.cursor_offset_in_obj
		cur_x, cur_y = self.cursor_pos_height()[:2]

		i = self.cursor_idx

		# skip objects in the same line
		while i < len(self.objs) and self.objs[i].y() <= cur_y:
			i += 1

		# find object in cur_x
		while i < len(self.objs):
			if self.objs[i].x() + self.objs[i].width() > cur_x:
				break

			if i+1 < len(self.objs):
				if self.objs[i+1].y() != self.objs[i].y():
					break
			i += 1

		if i >= len(self.objs):
			# not found
			self.cursor_idx = len(self.objs)
			self.cursor_offset_in_obj = 0
		else:
			self.cursor_idx = i
			o = self.objs[i]
			for off in range(o.length()):
				off += 1
				if o.x() + o.width(off) >= cur_x:
					if abs(o.x() + o.width(off) - cur_x) > abs(o.x() + o.width(off-1) - cur_x):
						off -= 1
					break;

			#assert off <= o.length()
			if off == o.length():
				off -= 1
			self.cursor_idx = i
			self.cursor_offset_in_obj = off

		self.formatting()
		self.cursor.queue_update()
		return True

