diff --git a/src/chat_control.py b/src/chat_control.py
index 890bc5c9c935d4da58fc4afae35de4da2134092a..5f0801afc588d782728f4cf0a87e8faa36e547f6 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -424,7 +424,8 @@ class ChatControlBase(MessageControl):
 		message_textview = widget
 		message_buffer = message_textview.get_buffer()
 		start_iter, end_iter = message_buffer.get_bounds()
-		message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
+		message = message_buffer.get_text(start_iter, end_iter, False).decode(
+			'utf-8')
 
 		# construct event instance from binding
 		event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
@@ -470,7 +471,8 @@ class ChatControlBase(MessageControl):
 				self.send_message(message) # send the message
 		else:
 			# Give the control itself a chance to process
-			self.handle_message_textview_mykey_press(widget, event_keyval, event_keymod)
+			self.handle_message_textview_mykey_press(widget, event_keyval,
+				event_keymod)
 
 	def _process_command(self, message):
 		if not message:
@@ -533,7 +535,7 @@ class ChatControlBase(MessageControl):
 	def print_conversation_line(self, text, kind, name, tim,
 		other_tags_for_name = [], other_tags_for_time = [], 
 		other_tags_for_text = [], count_as_new = True,
-		subject = None, old_kind = None):
+		subject = None, old_kind = None, xhtml = None):
 		'''prints 'chat' type messages'''
 		jid = self.contact.jid
 		full_jid = self.get_full_jid()
@@ -543,7 +545,7 @@ class ChatControlBase(MessageControl):
 			end = True
 		textview.print_conversation_line(text, jid, kind, name, tim,
 			other_tags_for_name, other_tags_for_time, other_tags_for_text,
-			subject, old_kind)
+			subject, old_kind, xhtml)
 
 		if not count_as_new:
 			return
@@ -765,7 +767,8 @@ class ChatControlBase(MessageControl):
 			#whatever is already typed
 			start_iter = conv_buf.get_start_iter()
 			end_iter = conv_buf.get_end_iter()
-			self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode('utf-8')
+			self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode(
+				'utf-8')
 			self.typing_new = False
 		if direction == 'up':
 			if self.sent_history_pos == 0:
@@ -825,8 +828,8 @@ class ChatControl(ChatControlBase):
 	old_msg_kind = None # last kind of the printed message
 	
 	def __init__(self, parent_win, contact, acct, resource = None):
-		ChatControlBase.__init__(self, self.TYPE_ID, parent_win, 'chat_child_vbox',
-			(_('Chat'), _('Chats')), contact, acct, resource)
+		ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
+			'chat_child_vbox', (_('Chat'), _('Chats')), contact, acct, resource)
 			
 		# for muc use:
 		# widget = self.xml.get_widget('muc_window_actions_button')
@@ -834,13 +837,16 @@ class ChatControl(ChatControlBase):
 		id = widget.connect('clicked', self.on_actions_button_clicked)
 		self.handlers[id] = widget
 
-		self.hide_chat_buttons_always = gajim.config.get('always_hide_chat_buttons')
+		self.hide_chat_buttons_always = gajim.config.get(
+			'always_hide_chat_buttons')
 		self.chat_buttons_set_visible(self.hide_chat_buttons_always)
-		self.widget_set_visible(self.xml.get_widget('banner_eventbox'), gajim.config.get('hide_chat_banner'))
+		self.widget_set_visible(self.xml.get_widget('banner_eventbox'),
+			gajim.config.get('hide_chat_banner'))
 		# Initialize drag-n-drop
 		self.TARGET_TYPE_URI_LIST = 80
 		self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ) ]
-		id = self.widget.connect('drag_data_received', self._on_drag_data_received)
+		id = self.widget.connect('drag_data_received',
+			self._on_drag_data_received)
 		self.handlers[id] = self.widget
 		self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
 			gtk.DEST_DEFAULT_HIGHLIGHT |
@@ -862,17 +868,21 @@ class ChatControl(ChatControlBase):
 			self._on_window_motion_notify)
 		self.handlers[id] = self.parent_win.window
 		message_tv_buffer = self.msg_textview.get_buffer()
-		id = message_tv_buffer.connect('changed', self._on_message_tv_buffer_changed)
+		id = message_tv_buffer.connect('changed',
+			self._on_message_tv_buffer_changed)
 		self.handlers[id] = message_tv_buffer
 		
 		widget = self.xml.get_widget('avatar_eventbox')
-		id = widget.connect('enter-notify-event', self.on_avatar_eventbox_enter_notify_event)
+		id = widget.connect('enter-notify-event',
+			self.on_avatar_eventbox_enter_notify_event)
 		self.handlers[id] = widget
 
-		id = widget.connect('leave-notify-event', self.on_avatar_eventbox_leave_notify_event)
+		id = widget.connect('leave-notify-event',
+			self.on_avatar_eventbox_leave_notify_event)
 		self.handlers[id] = widget
 
-		id = widget.connect('button-press-event', self.on_avatar_eventbox_button_press_event)
+		id = widget.connect('button-press-event',
+			self.on_avatar_eventbox_button_press_event)
 		self.handlers[id] = widget
 
 		widget = self.xml.get_widget('gpg_togglebutton')
@@ -1166,8 +1176,8 @@ class ChatControl(ChatControlBase):
 			if current_state == 'composing':
 				self.send_chatstate('paused') # pause composing
 
-		# assume no activity and let the motion-notify or 'insert-text' make them True
-		# refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
+		# assume no activity and let the motion-notify or 'insert-text' make them
+		# True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
 		self.reset_kbd_mouse_timeout_vars()
 		return True # loop forever		
 
@@ -1186,11 +1196,12 @@ class ChatControl(ChatControlBase):
 		if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
 			return True # loop forever
 
-		if not self.mouse_over_in_last_30_secs or self.kbd_activity_in_last_30_secs:
+		if not self.mouse_over_in_last_30_secs or \
+		self.kbd_activity_in_last_30_secs:
 			self.send_chatstate('inactive', contact)
 
-		# assume no activity and let the motion-notify or 'insert-text' make them True
-		# refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
+		# assume no activity and let the motion-notify or 'insert-text' make them
+		# True refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
 		self.reset_kbd_mouse_timeout_vars()
 		return True # loop forever
 
@@ -1201,7 +1212,7 @@ class ChatControl(ChatControlBase):
 		self.kbd_activity_in_last_30_secs = False
 
 	def print_conversation(self, text, frm = '', tim = None,
-		encrypted = False, subject = None):
+		encrypted = False, subject = None, xhtml = None):
 		'''Print a line in the conversation:
 		if contact is set to status: it's a status message
 		if contact is set to another value: it's an outgoing message
@@ -1241,7 +1252,7 @@ class ChatControl(ChatControlBase):
 				kind = 'outgoing'
 				name = gajim.nicks[self.account]
 		ChatControlBase.print_conversation_line(self, text, kind, name, tim,
-			subject = subject, old_kind = self.old_msg_kind)
+			subject = subject, old_kind = self.old_msg_kind, xhtml = xhtml)
 		if text.startswith('/me ') or text.startswith('/me\n'):
 			self.old_msg_kind = None
 		else:
@@ -1459,18 +1470,20 @@ class ChatControl(ChatControlBase):
 
 		# prevent going paused if we we were not composing (JEP violation)
 		if state == 'paused' and not contact.our_chatstate == 'composing':
-			MessageControl.send_message(self, None, chatstate = 'active') # go active before
+			# go active before
+			MessageControl.send_message(self, None, chatstate = 'active')
 			contact.our_chatstate = 'active'
 			self.reset_kbd_mouse_timeout_vars()
 		
 		# if we're inactive prevent composing (JEP violation)
 		elif contact.our_chatstate == 'inactive' and state == 'composing':
-			MessageControl.send_message(self, None, chatstate = 'active') # go active before
+			# go active before
+			MessageControl.send_message(self, None, chatstate = 'active')
 			contact.our_chatstate = 'active'
 			self.reset_kbd_mouse_timeout_vars()
 
-		MessageControl.send_message(self, None, chatstate = state, msg_id = contact.msg_id,
-									composing_jep = contact.composing_jep)
+		MessageControl.send_message(self, None, chatstate = state,
+			msg_id = contact.msg_id, composing_jep = contact.composing_jep)
 		contact.our_chatstate = state
 		if contact.our_chatstate == 'active':
 			self.reset_kbd_mouse_timeout_vars()
diff --git a/src/common/connection.py b/src/common/connection.py
index 2f4e0895a711801aa8c5b752c7e3722261e49f5d..803fca602f4b9ad77d12452009ff8a7f60e764c3 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -34,6 +34,8 @@ from common import GnuPG
 from connection_handlers import *
 USE_GPG = GnuPG.USE_GPG
 
+from rst_xhtml_generator import create_xhtml
+
 class Connection(ConnectionHandlers):
 	'''Connection class'''
 	def __init__(self, name):
@@ -650,6 +652,7 @@ class Connection(ConnectionHandlers):
 				p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
 			if self.connection:
 				self.connection.send(p)
+				self.priority = priority
 			self.dispatch('STATUS', show)
 
 	def _on_disconnected(self):
@@ -660,15 +663,18 @@ class Connection(ConnectionHandlers):
 	def get_status(self):
 		return STATUS_LIST[self.connected]
 
-	def send_motd(self, jid, subject = '', msg = ''):
+
+	def send_motd(self, jid, subject = '', msg = '', xhtml = None):
 		if not self.connection:
 			return
-		msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject)
+		msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject,
+			xhtml = xhtml)
+
 		self.connection.send(msg_iq)
 
 	def send_message(self, jid, msg, keyID, type = 'chat', subject='',
 	chatstate = None, msg_id = None, composing_jep = None, resource = None,
-	user_nick = None):
+	user_nick = None, xhtml = None):
 		if not self.connection:
 			return
 		if not msg and chatstate is None:
@@ -684,18 +690,20 @@ class Connection(ConnectionHandlers):
 			if msgenc:
 				msgtxt = '[This message is encrypted]'
 				lang = os.getenv('LANG')
-				if lang is not None or lang != 'en': # we're not english
-					msgtxt = _('[This message is encrypted]') +\
-						' ([This message is encrypted])' # one  in locale and one en
+				if lang is not None and lang != 'en': # we're not english
+					# one  in locale and one en
+					msgtxt = _('[This message is *encrypted* (See :JEP:`27`]') +\
+						' ([This message is *encrypted* (See :JEP:`27`])'
 		if type == 'chat':
-			msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type)
+			msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type,
+				xhtml = xhtml)
 		else:
 			if subject:
 				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
-					typ = 'normal', subject = subject)
+					typ = 'normal', subject = subject, xhtml = xhtml)
 			else:
 				msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
-					typ = 'normal')
+					typ = 'normal', xhtml = xhtml)
 		if msgenc:
 			msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
 
@@ -713,7 +721,8 @@ class Connection(ConnectionHandlers):
 				msg_iq.setTag(chatstate, namespace = common.xmpp.NS_CHATSTATES)
 			if composing_jep == 'JEP-0022' or not composing_jep:
 				# JEP-0022
-				chatstate_node = msg_iq.setTag('x', namespace = common.xmpp.NS_EVENT)
+				chatstate_node = msg_iq.setTag('x',
+					namespace = common.xmpp.NS_EVENT)
 				if not msgtxt: # when no <body>, add <id>
 					if not msg_id: # avoid putting 'None' in <id> tag
 						msg_id = ''
@@ -975,10 +984,10 @@ class Connection(ConnectionHandlers):
 			last_log = 0
 		self.last_history_line[jid]= last_log
 
-	def send_gc_message(self, jid, msg):
+	def send_gc_message(self, jid, msg, xhtml = None):
 		if not self.connection:
 			return
-		msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat')
+		msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml)
 		self.connection.send(msg_iq)
 		self.dispatch('MSGSENT', (jid, msg))
 
@@ -1104,8 +1113,8 @@ class Connection(ConnectionHandlers):
 		self.connection.send(iq)
 
 	def unregister_account(self, on_remove_success):
-		# no need to write this as a class method and keep the value of on_remove_success
-		# as a class property as pass it as an argument
+		# no need to write this as a class method and keep the value of
+		# on_remove_success as a class property as pass it as an argument
 		def _on_unregister_account_connect(con):
 			self.on_connect_auth = None
 			if gajim.account_is_connected(self.name):
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 411bfd251ffbe5ef7bf78d7fc97c8643f2a5eed1..cd4f0c8ef704e41873b5d5f25aac8ecfd67d30eb 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -1317,6 +1317,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
 	def _messageCB(self, con, msg):
 		'''Called when we receive a message'''
 		msgtxt = msg.getBody()
+		msghtml = msg.getXHTML()
 		mtype = msg.getType()
 		subject = msg.getSubject() # if not there, it's None
 		tim = msg.getTimestamp()
@@ -1402,7 +1403,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
 				has_timestamp = False
 				if msg.timestamp:
 					has_timestamp = True
-				self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp))
+				self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msghtml))
 				if self.name not in no_log_for and not int(float(time.mktime(tim))) <= \
 					self.last_history_line[jid] and msgtxt:
 					gajim.logger.write('gc_msg', frm, msgtxt, tim = tim)
@@ -1414,7 +1415,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
 				msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
 					subject = subject)
 			self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject,
-				chatstate, msg_id, composing_jep, user_nick))
+						chatstate, msg_id, composing_jep, user_nick, msghtml))
 		else: # it's single message
 			if self.name not in no_log_for and jid not in no_log_for and msgtxt:
 				gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
@@ -1428,7 +1429,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
 				self.dispatch('GC_INVITATION',(frm, jid_from, reason, password))
 			else:
 				self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
-					subject, chatstate, msg_id, composing_jep, user_nick))
+					subject, chatstate, msg_id, composing_jep, user_nick, msghtml))
 	# END messageCB
 
 	def _presenceCB(self, con, prs):
@@ -1445,8 +1446,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
 				# one
 				who = str(prs.getFrom())
 				jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
-				self.dispatch('GC_MSG', (jid_stripped, _('Nickname not allowed: %s') % \
-					resource, None, False))
+				self.dispatch('GC_MSG', (jid_stripped,
+					_('Nickname not allowed: %s') % resource, None, False, None))
 			return
 		jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
 		timestamp = None
diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py
index fee4cdb0c946a537abe59a17bcd840e06b2107f1..daf3097738572b77707716f51c8a8a3675fa81ec 100644
--- a/src/common/xmpp/protocol.py
+++ b/src/common/xmpp/protocol.py
@@ -397,10 +397,18 @@ class Message(Protocol):
     def getBody(self):
         """ Returns text of the message. """
         return self.getTagData('body')
-    def getXHTML(self):
-        """ Returns serialized xhtml-im body text of the message. """
+    def getXHTML(self, xmllang=None):
+        """ Returns serialized xhtml-im element text of the message.
+
+            TODO: Returning a DOM could make rendering faster."""
         xhtml = self.getTag('html')
-        return str(xhtml.getTag('body'))
+        if xhtml:
+            if xmllang:
+                body = xhtml.getTag('body', attrs={'xml:lang':xmllang})
+            else:
+                body = xhtml.getTag('body')
+            return str(body)
+        return None
     def getSubject(self):
         """ Returns subject of the message. """
         return self.getTagData('subject')
@@ -410,11 +418,22 @@ class Message(Protocol):
     def setBody(self,val):
         """ Sets the text of the message. """
         self.setTagData('body',val)
-    def setXHTML(self,val):
+
+    def setXHTML(self,val,xmllang=None):
         """ Sets the xhtml text of the message (JEP-0071).
             The parameter is the "inner html" to the body."""
-        dom = NodeBuilder(val)
-        self.setTag('html',namespace=NS_XHTML_IM).setTag('body',namespace=NS_XHTML).addChild(node=dom.getDom())
+        try:
+            if xmllang:
+                dom = NodeBuilder('<body xmlns="'+NS_XHTML+'" xml:lang="'+xmllang+'" >' + val + '</body>').getDom()
+            else:
+                dom = NodeBuilder('<body xmlns="'+NS_XHTML+'">'+val+'</body>',0).getDom()
+            if self.getTag('html'):
+                self.getTag('html').addChild(node=dom)
+            else:
+                self.setTag('html',namespace=NS_XHTML_IM).addChild(node=dom)
+        except Exception, e:
+            print "Error", e
+            pass #FIXME: log. we could not set xhtml (parse error, whatever)
     def setSubject(self,val):
         """ Sets the subject of the message. """
         self.setTagData('subject',val)
diff --git a/src/conversation_textview.py b/src/conversation_textview.py
index dcc4c2159f3d8fc77fc173174db9c96b10a6ef5d..18e907a6f68cb2f73d5efed5be994186b2e4683c 100644
--- a/src/conversation_textview.py
+++ b/src/conversation_textview.py
@@ -39,12 +39,16 @@ from common import helpers
 from calendar import timegm
 from common.fuzzyclock import FuzzyClock
 
+from htmltextview import HtmlTextView
+
+
 class ConversationTextview:
 	'''Class for the conversation textview (where user reads already said messages)
 	for chat/groupchat windows'''
 	def __init__(self, account):
 		# no need to inherit TextView, use it as property is safer
-		self.tv = gtk.TextView()
+		self.tv = HtmlTextView()
+		self.tv.html_hyperlink_handler = self.html_hyperlink_handler
 
 		# set properties
 		self.tv.set_border_width(1)
@@ -98,7 +102,7 @@ class ConversationTextview:
 		tag.set_property('weight', pango.WEIGHT_BOLD)
 
 		tag = buffer.create_tag('time_sometimes')
-		tag.set_property('foreground', 'grey')
+		tag.set_property('foreground', 'darkgrey')
 		tag.set_property('scale', pango.SCALE_SMALL)
 		tag.set_property('justification', gtk.JUSTIFY_CENTER)
 
@@ -141,6 +145,8 @@ class ConversationTextview:
 		
 		path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
 		self.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
+		# use it for hr too
+		self.tv.focus_out_line_pixbuf = self.focus_out_line_pixbuf
 
 	def del_handlers(self):
 		for i in self.handlers.keys():
@@ -504,6 +510,15 @@ class ConversationTextview:
 				# we launch the correct application
 				helpers.launch_browser_mailer(kind, word)
 
+	def html_hyperlink_handler(self, texttag, widget, event, iter, kind, href):
+		if event.type == gtk.gdk.BUTTON_PRESS:
+			if event.button == 3: # right click
+				self.make_link_menu(event, kind, href)
+			else:
+				# we launch the correct application
+				helpers.launch_browser_mailer(kind, href)
+
+
 	def detect_and_print_special_text(self, otext, other_tags):
 		'''detects special text (emots & links & formatting)
 		prints normal text before any special text it founts,
@@ -637,11 +652,11 @@ class ConversationTextview:
 	def print_empty_line(self):
 		buffer = self.tv.get_buffer()
 		end_iter = buffer.get_end_iter()
-		buffer.insert(end_iter, '\n')
+		buffer.insert_with_tags_by_name(end_iter, '\n', 'eol')
 
 	def print_conversation_line(self, text, jid, kind, name, tim,
-			other_tags_for_name = [], other_tags_for_time = [],
-			other_tags_for_text = [], subject = None, old_kind = None):
+	other_tags_for_name = [], other_tags_for_time = [], other_tags_for_text = [],
+	subject = None, old_kind = None, xhtml = None):
 		'''prints 'chat' type messages'''
 		buffer = self.tv.get_buffer()
 		buffer.begin_user_action()
@@ -651,7 +666,7 @@ class ConversationTextview:
 			at_the_end = True
 
 		if buffer.get_char_count() > 0:
-			buffer.insert(end_iter, '\n')
+			buffer.insert_with_tags_by_name(end_iter, '\n', 'eol')
 		if kind == 'incoming_queue':
 			kind = 'incoming'
 		if old_kind == 'incoming_queue':
@@ -726,7 +741,7 @@ class ConversationTextview:
 			else:
 				self.print_name(name, kind, other_tags_for_name)
 		self.print_subject(subject)
-		self.print_real_text(text, text_tags, name)
+		self.print_real_text(text, text_tags, name, xhtml)
 
 		# scroll to the end of the textview
 		if at_the_end or kind == 'outgoing':
@@ -763,8 +778,18 @@ class ConversationTextview:
 			buffer.insert(end_iter, subject)
 			self.print_empty_line()
 
-	def print_real_text(self, text, text_tags = [], name = None):
+	def print_real_text(self, text, text_tags = [], name = None, xhtml = None):
 		'''this adds normal and special text. call this to add text'''
+		if xhtml:
+			try:
+				if name and (text.startswith('/me ') or text.startswith('/me\n')):
+					xhtml = xhtml.replace('/me', '<dfn>%s</dfn>'% (name,), 1)
+				self.tv.display_html(xhtml.encode('utf-8'))
+				return
+			except Exception, e:
+				gajim.log.debug(str("Error processing xhtml")+str(e))
+				gajim.log.debug(str("with |"+xhtml+"|"))
+
 		buffer = self.tv.get_buffer()
 		# /me is replaced by name if name is given
 		if name and (text.startswith('/me ') or text.startswith('/me\n')):
diff --git a/src/gajim.py b/src/gajim.py
index f7971ff556d11b0fba3e8e1330fe57708b91a5b5..1dd62b35fff56caffa44c3b41c8846768175a237 100755
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -508,13 +508,14 @@ class Interface:
 
 	def handle_event_msg(self, account, array):
 		# 'MSG' (account, (jid, msg, time, encrypted, msg_type, subject,
-		# chatstate, msg_id, composing_jep, user_nick)) user_nick is JEP-0172
+		# chatstate, msg_id, composing_jep, user_nick, xhtml)) user_nick is JEP-0172
 
 		full_jid_with_resource = array[0]
 		jid = gajim.get_jid_without_resource(full_jid_with_resource)
 		resource = gajim.get_resource_from_jid(full_jid_with_resource)
 
 		message = array[1]
+		encrypted = array[3]
 		msg_type = array[4]
 		subject = array[5]
 		chatstate = array[6]
@@ -598,18 +599,26 @@ class Interface:
 		if pm:
 			nickname = resource
 			msg_type = 'pm'
-			groupchat_control.on_private_message(nickname, message, array[2])
+			groupchat_control.on_private_message(nickname, message, array[2],
+				array[10])
 		else:
 			# array: (jid, msg, time, encrypted, msg_type, subject)
-			self.roster.on_message(jid, message, array[2], account, array[3],
-				msg_type, subject, resource, msg_id, array[9], advanced_notif_num)
+			if encrypted:
+				self.roster.on_message(jid, message, array[2], account, array[3],
+					msg_type, subject, resource, msg_id, array[9],
+					advanced_notif_num)
+			else:
+				#xhtml in last element
+				self.roster.on_message(jid, message, array[2], account, array[3],
+					msg_type, subject, resource, msg_id, array[9],
+					advanced_notif_num, xhtml = array[10])
 			nickname = gajim.get_name_from_jid(account, jid)
 		# Check and do wanted notifications	
 		msg = message
 		if subject:
 			msg = _('Subject: %s') % subject + '\n' + msg
-		notify.notify('new_message', full_jid_with_resource, account, [msg_type, first, nickname,
-			msg], advanced_notif_num)
+		notify.notify('new_message', full_jid_with_resource, account, [msg_type,
+			first, nickname, msg], advanced_notif_num)
 
 		if self.remote_ctrl:
 			self.remote_ctrl.raise_signal('NewMessage', (account, array))
@@ -699,8 +708,8 @@ class Interface:
 			self.remote_ctrl.raise_signal('Subscribed', (account, array))
 
 	def handle_event_unsubscribed(self, account, jid):
-		dialogs.InformationDialog(_('Contact "%s" removed subscription from you') % jid,
-				_('You will always see him or her as offline.'))
+		dialogs.InformationDialog(_('Contact "%s" removed subscription from you')\
+			% jid, _('You will always see him or her as offline.'))
 		# FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does not show deny
 		gajim.connections[account].ack_unsubscribed(jid)
 		if self.remote_ctrl:
@@ -743,8 +752,8 @@ class Interface:
 			config.ServiceRegistrationWindow(array[0], array[1], account,
 				array[2])
 		else:
-			dialogs.ErrorDialog(_('Contact with "%s" cannot be established')\
-% array[0], _('Check your connection or try again later.'))
+			dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \
+				% array[0], _('Check your connection or try again later.'))
 
 	def handle_event_agent_info_items(self, account, array):
 		#('AGENT_INFO_ITEMS', account, (agent, node, items))
@@ -878,8 +887,8 @@ class Interface:
 		# Get the window and control for the updated status, this may be a PrivateChatControl
 		control = self.msg_win_mgr.get_control(room_jid, account)
 		if control:
-			control.chg_contact_status(nick, show, status, array[4], array[5], array[6],
-						array[7], array[8], array[9], array[10])
+			control.chg_contact_status(nick, show, status, array[4], array[5],
+				array[6], array[7], array[8], array[9], array[10])
 
 		# print status in chat window and update status/GPG image
 		if self.msg_win_mgr.has_window(fjid, account):
@@ -897,7 +906,7 @@ class Interface:
 
 
 	def handle_event_gc_msg(self, account, array):
-		# ('GC_MSG', account, (jid, msg, time, has_timestamp))
+		# ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg))
 		jids = array[0].split('/', 1)
 		room_jid = jids[0]
 		gc_control = self.msg_win_mgr.get_control(room_jid, account)
@@ -909,7 +918,7 @@ class Interface:
 		else:
 			# message from someone
 			nick = jids[1]
-		gc_control.on_message(nick, array[1], array[2], array[3])
+		gc_control.on_message(nick, array[1], array[2], array[3], array[4])
 		if self.remote_ctrl:
 			self.remote_ctrl.raise_signal('GCMessage', (account, array))
 
@@ -926,7 +935,8 @@ class Interface:
 			gc_control.print_conversation(array[2])
 		# ... Or the message comes from the occupant who set the subject
 		elif len(jids) > 1:
-			gc_control.print_conversation('%s has set the subject to %s' % (jids[1], array[1]))
+			gc_control.print_conversation('%s has set the subject to %s' % (
+				jids[1], array[1]))
 
 	def handle_event_gc_config(self, account, array):
 		#('GC_CONFIG', account, (jid, config))  config is a dict
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
index 339a3b8a136cba5da815524ffbda54e4335836b5..3590163d93696ab305879c649786c1938fa53c2f 100644
--- a/src/groupchat_control.py
+++ b/src/groupchat_control.py
@@ -103,9 +103,10 @@ class PrivateChatControl(ChatControl):
 		if not message:
 			return
 
-		# We need to make sure that we can still send through the room and that the
-		# recipient did not go away
-		contact = gajim.contacts.get_first_contact_from_jid(self.account, self.contact.jid)
+		# We need to make sure that we can still send through the room and that
+		# the recipient did not go away
+		contact = gajim.contacts.get_first_contact_from_jid(self.account,
+			self.contact.jid)
 		if contact is None:
 			# contact was from pm in MUC
 			room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid)
@@ -384,7 +385,8 @@ class GroupchatControl(ChatControlBase):
 				color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
 			elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\
 					not self.attention_flag:
-				color_name = gajim.config.get_per('themes', theme, 'state_muc_msg_color')
+				color_name = gajim.config.get_per('themes', theme,
+					'state_muc_msg_color')
 		if color_name:
 			color = gtk.gdk.colormap_get_system().alloc_color(color_name)
 			
@@ -433,18 +435,18 @@ class GroupchatControl(ChatControlBase):
 			childs[3].set_sensitive(False)
 		return menu
 
-	def on_message(self, nick, msg, tim, has_timestamp = False):
+	def on_message(self, nick, msg, tim, has_timestamp = False, xhtml = None):
 		if not nick:
 			# message from server
-			self.print_conversation(msg, tim = tim)
+			self.print_conversation(msg, tim = tim, xhtml = xhtml)
 		else:
 			# message from someone
 			if has_timestamp:
-				self.print_old_conversation(msg, nick, tim)
+				self.print_old_conversation(msg, nick, tim, xhtml)
 			else:
-				self.print_conversation(msg, nick, tim)
+				self.print_conversation(msg, nick, tim, xhtml)
 
-	def on_private_message(self, nick, msg, tim):
+	def on_private_message(self, nick, msg, tim, xhtml):
 		# Do we have a queue?
 		fjid = self.room_jid + '/' + nick
 		no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
@@ -452,7 +454,7 @@ class GroupchatControl(ChatControlBase):
 		# We print if window is opened
 		pm_control = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
 		if pm_control:
-			pm_control.print_conversation(msg, tim = tim)
+			pm_control.print_conversation(msg, tim = tim, xhtml = xhtml)
 			return
 
 		event = gajim.events.create_event('pm', (msg, '', 'incoming', tim,
@@ -505,7 +507,7 @@ class GroupchatControl(ChatControlBase):
 	gc_count_nicknames_colors = 0
 	gc_custom_colors = {}  
 
-	def print_old_conversation(self, text, contact, tim = None):
+	def print_old_conversation(self, text, contact, tim = None, xhtml = None):
 		if isinstance(text, str):
 			text = unicode(text, 'utf-8')
 		if contact == self.nick: # it's us
@@ -518,9 +520,9 @@ class GroupchatControl(ChatControlBase):
 			small_attr = []
 		ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
 			small_attr, small_attr + ['restored_message'],
-			small_attr + ['restored_message'])
+			small_attr + ['restored_message'], xhtml = xhtml)
 
-	def print_conversation(self, text, contact = '', tim = None):
+	def print_conversation(self, text, contact = '', tim = None, xhtml = None):
 		'''Print a line in the conversation:
 		if contact is set: it's a message from someone or an info message (contact
 		= 'info' in such a case)
@@ -574,7 +576,7 @@ class GroupchatControl(ChatControlBase):
 			self.check_and_possibly_add_focus_out_line()
 
 		ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
-			other_tags_for_name, [], other_tags_for_text)
+			other_tags_for_name, [], other_tags_for_text, xhtml = xhtml)
 
 	def get_nb_unread(self):
 		nb = len(gajim.events.get_events(self.account, self.room_jid,
@@ -643,13 +645,16 @@ class GroupchatControl(ChatControlBase):
 						word[char_position:char_position+1]
 					if (refer_to_nick_char != ''):
 						refer_to_nick_char_code = ord(refer_to_nick_char)
-						if ((refer_to_nick_char_code < 65 or refer_to_nick_char_code > 123)\
-							or (refer_to_nick_char_code < 97 and refer_to_nick_char_code > 90)):
+						if ((refer_to_nick_char_code < 65 or \
+						refer_to_nick_char_code > 123) or \
+						(refer_to_nick_char_code < 97 and \
+						refer_to_nick_char_code > 90)):
 							return True
 						else: 
-							# This is A->Z or a->z, we can be sure our nick is the beginning
-							# of a real word, do not highlight. Note that we can probably 
-							# do a better detection of non-punctuation characters
+							# This is A->Z or a->z, we can be sure our nick is the
+							# beginning of a real word, do not highlight. Note that we
+							# can probably do a better detection of non-punctuation
+							# characters
 							return False
 					else: # Special word == word, no char after in word
 						return True 
@@ -698,7 +703,8 @@ class GroupchatControl(ChatControlBase):
 						gc_contact.affiliation, gc_contact.status,
 						gc_contact.jid)
 
-	def on_send_pm(self, widget=None, model=None, iter=None, nick=None, msg=None):
+	def on_send_pm(self, widget = None, model = None, iter = None, nick = None,
+	msg = None):
 		'''opens a chat window and msg is not None sends private message to a
 		contact in a room'''
 		if nick is None:
@@ -707,14 +713,16 @@ class GroupchatControl(ChatControlBase):
 
 		self._start_private_message(nick)
 		if msg:
-			gajim.interface.msg_win_mgr.get_control(fjid, self.account).send_message(msg)
+			gajim.interface.msg_win_mgr.get_control(fjid, self.account).\
+				send_message(msg)
 
 	def draw_contact(self, nick, selected=False, focus=False):
 		iter = self.get_contact_iter(nick)
 		if not iter:
 			return
 		model = self.list_treeview.get_model()
-		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+			nick)
 		state_images = gajim.interface.roster.jabber_state_images['16']
 		if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)):
 			image = state_images['message']
@@ -752,8 +760,8 @@ class GroupchatControl(ChatControlBase):
 			scaled_pixbuf = None
 		model[iter][C_AVATAR] = scaled_pixbuf
 
-	def chg_contact_status(self, nick, show, status, role, affiliation, jid, reason, actor,
-				statusCode, new_nick):
+	def chg_contact_status(self, nick, show, status, role, affiliation, jid,
+	reason, actor, statusCode, new_nick):
 		'''When an occupant changes his or her status'''
 		if show == 'invisible':
 			return
@@ -845,7 +853,8 @@ class GroupchatControl(ChatControlBase):
 					self.add_contact_to_roster(nick, show, role,
 						affiliation, status, jid)
 				else:
-					c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+					c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+						nick)
 					if c.show == show and c.status == status and \
 						c.affiliation == affiliation: #no change
 						return
@@ -948,7 +957,8 @@ class GroupchatControl(ChatControlBase):
 		iter = self.get_contact_iter(nick)
 		if not iter:
 			return
-		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+			nick)
 		if gc_contact:
 			gajim.contacts.remove_gc_contact(self.account, gc_contact)
 		parent_iter = model.iter_parent(iter)
@@ -1096,7 +1106,8 @@ class GroupchatControl(ChatControlBase):
 			if len(message_array):
 				message_array = message_array[0].split()
 				nick = message_array.pop(0)
-				room_nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
+				room_nicks = gajim.contacts.get_nick_list(self.account,
+					self.room_jid)
 				reason = ' '.join(message_array)
 				if nick in room_nicks:
 					ban_jid = gajim.construct_fjid(self.room_jid, nick)
@@ -1117,7 +1128,8 @@ class GroupchatControl(ChatControlBase):
 			if len(message_array):
 				message_array = message_array[0].split()
 				nick = message_array.pop(0)
-				room_nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
+				room_nicks = gajim.contacts.get_nick_list(self.account,
+					self.room_jid)
 				reason = ' '.join(message_array)
 				if nick in room_nicks:
 					gajim.connections[self.account].gc_set_role(self.room_jid, nick,
@@ -1177,7 +1189,8 @@ class GroupchatControl(ChatControlBase):
 
 			if not self._process_command(message):
 				# Send the message
-				gajim.connections[self.account].send_gc_message(self.room_jid, message)
+				gajim.connections[self.account].send_gc_message(self.room_jid,
+					message)
 				self.msg_textview.get_buffer().set_text('')
 				self.msg_textview.grab_focus()
 
@@ -1200,7 +1213,8 @@ class GroupchatControl(ChatControlBase):
 			self.print_conversation(_('Usage: /%s [reason], closes the current '
 				'window or tab, displaying reason if specified.') % command, 'info')
 		elif command == 'compact':
-			self.print_conversation(_('Usage: /%s, hide the chat buttons.') % command, 'info')
+			self.print_conversation(_('Usage: /%s, hide the chat buttons.') % \
+				command, 'info')
 		elif command == 'invite':
 			self.print_conversation(_('Usage: /%s <JID> [reason], invites JID to '
 				'the current room, optionally providing a reason.') % command,
@@ -1241,7 +1255,8 @@ class GroupchatControl(ChatControlBase):
 			self.print_conversation(_('No help info for /%s') % command, 'info')
 
 	def get_role(self, nick):
-		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
+		gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
+			nick)
 		if gc_contact:
 			return gc_contact.role
 		else:
@@ -1327,7 +1342,8 @@ class GroupchatControl(ChatControlBase):
 			_('Please specify the new subject:'), self.subject)
 		response = instance.get_response()
 		if response == gtk.RESPONSE_OK:
-			# Note, we don't update self.subject since we don't know whether it will work yet
+			# Note, we don't update self.subject since we don't know whether it
+			# will work yet
 			subject = instance.input_entry.get_text().decode('utf-8')
 			gajim.connections[self.account].send_gc_subject(self.room_jid, subject)
 
@@ -1372,7 +1388,8 @@ class GroupchatControl(ChatControlBase):
 				_('Bookmark has been added successfully'),
 				_('You can manage your bookmarks via Actions menu in your roster.'))
 
-	def handle_message_textview_mykey_press(self, widget, event_keyval, event_keymod):
+	def handle_message_textview_mykey_press(self, widget, event_keyval,
+	event_keymod):
 		# NOTE: handles mykeypress which is custom signal connected to this
 		# CB in new_room(). for this singal see message_textview.py
 
@@ -1384,12 +1401,14 @@ class GroupchatControl(ChatControlBase):
 
 		message_buffer = widget.get_buffer()
 		start_iter, end_iter = message_buffer.get_bounds()
-		message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
+		message = message_buffer.get_text(start_iter, end_iter, False).decode(
+			'utf-8')
 
 		if event.keyval == gtk.keysyms.Tab: # TAB
 			cursor_position = message_buffer.get_insert()
 			end_iter = message_buffer.get_iter_at_mark(cursor_position)
-			text = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
+			text = message_buffer.get_text(start_iter, end_iter, False).decode(
+				'utf-8')
 			if text.endswith(' '):
 				if not self.last_key_tabs:
 					return False
@@ -1505,8 +1524,8 @@ class GroupchatControl(ChatControlBase):
 
 		# looking for user's affiliation and role
 		user_nick = self.nick
-		user_affiliation = gajim.contacts.get_gc_contact(self.account, self.room_jid,
-			user_nick).affiliation
+		user_affiliation = gajim.contacts.get_gc_contact(self.account,
+			self.room_jid, user_nick).affiliation
 		user_role = self.get_role(user_nick)
 
 		# making menu from glade
@@ -1515,9 +1534,10 @@ class GroupchatControl(ChatControlBase):
 		# these conditions were taken from JEP 0045
 		item = xml.get_widget('kick_menuitem')
 		if user_role != 'moderator' or \
-			(user_affiliation == 'admin' and target_affiliation == 'owner') or \
-			(user_affiliation == 'member' and target_affiliation in ('admin', 'owner')) or \
-			(user_affiliation == 'none' and target_affiliation != 'none'):
+		(user_affiliation == 'admin' and target_affiliation == 'owner') or \
+		(user_affiliation == 'member' and target_affiliation in ('admin',
+		'owner')) or (user_affiliation == 'none' and target_affiliation != \
+		'none'):
 			item.set_sensitive(False)
 		id = item.connect('activate', self.kick, nick)
 		self.handlers[id] = item
@@ -1525,18 +1545,18 @@ class GroupchatControl(ChatControlBase):
 		item = xml.get_widget('voice_checkmenuitem')
 		item.set_active(target_role != 'visitor')
 		if user_role != 'moderator' or \
-			user_affiliation == 'none' or \
-			(user_affiliation=='member' and target_affiliation!='none') or \
-			target_affiliation in ('admin', 'owner'):
+		user_affiliation == 'none' or \
+		(user_affiliation=='member' and target_affiliation!='none') or \
+		target_affiliation in ('admin', 'owner'):
 			item.set_sensitive(False)
 		id = item.connect('activate', self.on_voice_checkmenuitem_activate, 
-					nick)
+			nick)
 		self.handlers[id] = item
 
 		item = xml.get_widget('moderator_checkmenuitem')
 		item.set_active(target_role == 'moderator')
 		if not user_affiliation in ('admin', 'owner') or \
-			target_affiliation in ('admin', 'owner'):
+		target_affiliation in ('admin', 'owner'):
 			item.set_sensitive(False)
 		id = item.connect('activate', self.on_moderator_checkmenuitem_activate,
 					nick)
@@ -1544,8 +1564,8 @@ class GroupchatControl(ChatControlBase):
 	
 		item = xml.get_widget('ban_menuitem')
 		if not user_affiliation in ('admin', 'owner') or \
-			(target_affiliation in ('admin', 'owner') and\
-			user_affiliation != 'owner'):
+		(target_affiliation in ('admin', 'owner') and\
+		user_affiliation != 'owner'):
 			item.set_sensitive(False)
 		id = item.connect('activate', self.ban, jid)
 		self.handlers[id] = item
@@ -1553,7 +1573,7 @@ class GroupchatControl(ChatControlBase):
 		item = xml.get_widget('member_checkmenuitem')
 		item.set_active(target_affiliation != 'none')
 		if not user_affiliation in ('admin', 'owner') or \
-			(user_affiliation != 'owner' and target_affiliation in ('admin','owner')):
+		(user_affiliation != 'owner' and target_affiliation in ('admin','owner')):
 			item.set_sensitive(False)
 		id = item.connect('activate', self.on_member_checkmenuitem_activate, 
 					jid)
@@ -1743,19 +1763,23 @@ class GroupchatControl(ChatControlBase):
 
 	def grant_voice(self, widget, nick):
 		'''grant voice privilege to a user'''
-		gajim.connections[self.account].gc_set_role(self.room_jid, nick, 'participant')
+		gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+			'participant')
 
 	def revoke_voice(self, widget, nick):
 		'''revoke voice privilege to a user'''
-		gajim.connections[self.account].gc_set_role(self.room_jid, nick, 'visitor')
+		gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+			'visitor')
 
 	def grant_moderator(self, widget, nick):
 		'''grant moderator privilege to a user'''
-		gajim.connections[self.account].gc_set_role(self.room_jid, nick, 'moderator')
+		gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+			'moderator')
 
 	def revoke_moderator(self, widget, nick):
 		'''revoke moderator privilege to a user'''
-		gajim.connections[self.account].gc_set_role(self.room_jid, nick, 'participant')
+		gajim.connections[self.account].gc_set_role(self.room_jid, nick,
+			'participant')
 
 	def ban(self, widget, jid):
 		'''ban a user'''
@@ -1770,17 +1794,17 @@ class GroupchatControl(ChatControlBase):
 		else:
 			return # stop banning procedure
 		gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
-								'outcast', reason)
+			'outcast', reason)
 
 	def grant_membership(self, widget, jid):
 		'''grant membership privilege to a user'''
 		gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
-								'member')
+			'member')
 
 	def revoke_membership(self, widget, jid):
 		'''revoke membership privilege to a user'''
 		gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
-								'none')
+			'none')
 
 	def grant_admin(self, widget, jid):
 		'''grant administrative privilege to a user'''
@@ -1804,7 +1828,8 @@ class GroupchatControl(ChatControlBase):
 		c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
 		c2 = gajim.contacts.contact_from_gc_contact(c)
 		if gajim.interface.instances[self.account]['infos'].has_key(c2.jid):
-			gajim.interface.instances[self.account]['infos'][c2.jid].window.present()
+			gajim.interface.instances[self.account]['infos'][c2.jid].window.\
+				present()
 		else:
 			gajim.interface.instances[self.account]['infos'][c2.jid] = \
 				vcard.VcardWindow(c2, self.account, is_fake = True)
diff --git a/src/htmltextview.py b/src/htmltextview.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a57a2adbe35482fec76db5c6f078370b00dfb1c
--- /dev/null
+++ b/src/htmltextview.py
@@ -0,0 +1,968 @@
+### Copyright (C) 2005 Gustavo J. A. M. Carneiro
+### Copyright (C) 2006 Santiago Gala
+###
+### This library is free software; you can redistribute it and/or
+### modify it under the terms of the GNU Lesser General Public
+### License as published by the Free Software Foundation; either
+### version 2 of the License, or (at your option) any later version.
+###
+### This library 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
+### Lesser General Public License for more details.
+###
+### You should have received a copy of the GNU Lesser General Public
+### License along with this library; if not, write to the
+### Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+### Boston, MA 02111-1307, USA.
+
+
+"""
+A gtk.TextView-based renderer for XHTML-IM, as described in:
+  http://www.jabber.org/jeps/jep-0071.html
+
+Starting with the version posted by Gustavo Carneiro,
+I (Santiago Gala) am trying to make it more compatible
+with the markup that docutils generate, and also more
+modular.
+
+"""
+
+import gobject
+import pango
+import gtk
+import xml.sax, xml.sax.handler
+import re
+import warnings
+from cStringIO import StringIO
+import urllib2
+import operator
+
+#from common import i18n
+
+
+import tooltips
+
+
+__all__ = ['HtmlTextView']
+
+whitespace_rx = re.compile("\\s+")
+allwhitespace_rx = re.compile("^\\s*$")
+
+## pixels = points * display_resolution
+display_resolution = 0.3514598*(gtk.gdk.screen_height() /
+					float(gtk.gdk.screen_height_mm()))
+
+#embryo of CSS classes
+classes = {
+	#'system-message':';display: none',
+	'problematic':';color: red',
+}
+
+#styles for elemens
+element_styles = {
+		'u'			: ';text-decoration: underline',
+		'em'		: ';font-style: oblique',
+		'cite'		: '; background-color:rgb(170,190,250); font-style: oblique',
+		'li'		: '; margin-left: 1em; margin-right: 10%',
+		'strong'	: ';font-weight: bold',
+		'pre'		: '; background-color:rgb(190,190,190); font-family: monospace; white-space: pre; margin-left: 1em; margin-right: 10%',
+		'kbd'		: ';background-color:rgb(210,210,210);font-family: monospace',
+		'blockquote': '; background-color:rgb(170,190,250); margin-left: 2em; margin-right: 10%',
+		'dt'		: ';font-weight: bold; font-style: oblique',
+		'dd'		: ';margin-left: 2em; font-style: oblique'
+}
+# no difference for the moment
+element_styles['dfn'] = element_styles['em']
+element_styles['var'] = element_styles['em']
+# deprecated, legacy, presentational
+element_styles['tt']  = element_styles['kbd']
+element_styles['i']   = element_styles['em']
+element_styles['b']   = element_styles['strong']
+
+class_styles = {
+}
+
+"""
+==========
+  JEP-0071
+==========
+
+This Integration Set includes a subset of the modules defined for 
+XHTML 1.0 but does not redefine any existing modules, nor 
+does it define any new modules. Specifically, it includes the 
+following modules only:
+
+- Structure
+- Text
+  
+  * Block
+    
+    phrasal
+       addr, blockquote, pre
+    Struc
+       div,p
+    Heading
+       h1, h2, h3, h4, h5, h6
+    
+  * Inline
+    
+    phrasal
+       abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var
+    structural
+       br, span
+  
+- Hypertext (a)
+- List (ul, ol, dl)
+- Image (img)
+- Style Attribute
+     
+Therefore XHTML-IM uses the following content models:
+
+  Block.mix
+            Block-like elements, e.g., paragraphs
+  Flow.mix
+            Any block or inline elements
+  Inline.mix
+            Character-level elements
+  InlineNoAnchor.class
+			Anchor element 
+  InlinePre.mix
+            Pre element
+
+XHTML-IM also uses the following Attribute Groups:
+
+Core.extra.attrib
+	TBD
+I18n.extra.attrib
+	TBD
+Common.extra
+	style
+
+
+...
+#block level:
+#Heading    h
+#           ( pres           = h1 | h2 | h3 | h4 | h5 | h6 )
+#Block      ( phrasal        = address | blockquote | pre )
+#NOT           ( presentational = hr )
+#           ( structural     = div | p )
+#other:     section
+#Inline     ( phrasal        = abbr | acronym | cite | code | dfn | em | kbd | q | samp | strong | var )
+#NOT        ( presentational =  b  | big | i | small | sub | sup | tt )
+#           ( structural     =  br | span )
+#Param/Legacy    param, font, basefont, center, s, strike, u, dir, menu, isindex
+#
+"""
+
+BLOCK_HEAD = set(( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', ))
+BLOCK_PHRASAL = set(( 'address', 'blockquote', 'pre', ))
+BLOCK_PRES = set(( 'hr', )) #not in xhtml-im
+BLOCK_STRUCT = set(( 'div', 'p', ))
+BLOCK_HACKS = set(( 'table', 'tr' ))
+BLOCK = BLOCK_HEAD.union(BLOCK_PHRASAL).union(BLOCK_STRUCT).union(BLOCK_PRES).union(BLOCK_HACKS)
+
+INLINE_PHRASAL = set('abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var'.split(', '))
+INLINE_PRES = set('b, i, u, tt'.split(', ')) #not in xhtml-im
+INLINE_STRUCT = set('br, span'.split(', '))
+INLINE = INLINE_PHRASAL.union(INLINE_PRES).union(INLINE_STRUCT)
+
+LIST_ELEMS = set( 'dl, ol, ul'.split(', '))
+
+for name in BLOCK_HEAD:
+	num = eval(name[1])
+	size = (num-1) // 2
+	weigth = (num - 1) % 2
+	element_styles[name] = '; font-size: %s; %s' % ( ('large', 'medium', 'small')[size],
+													('font-weight: bold', 'font-style: oblique')[weigth],
+											  )
+
+
+def build_patterns(view, config, interface):
+	#extra, rst does not mark _underline_ or /it/ up
+	#actually <b>, <i> or <u> are not in the JEP-0071, but are seen in the wild
+	basic_pattern = r'(?<!\w|\<|/|:)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\w|/|:)|'\
+					r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
+	view.basic_pattern_re = re.compile(basic_pattern)
+	#TODO: emoticons
+	emoticons_pattern = ''
+	if config.get('emoticons_theme'):
+		# When an emoticon is bordered by an alpha-numeric character it is NOT
+		# expanded.  e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc.
+		# We still allow multiple emoticons side-by-side like :P:P:P
+		# sort keys by length so :qwe emot is checked before :q
+		keys = interface.emoticons.keys()
+		keys.sort(interface.on_emoticon_sort)
+		emoticons_pattern_prematch = ''
+		emoticons_pattern_postmatch = ''
+		emoticon_length = 0
+		for emoticon in keys: # travel thru emoticons list
+			emoticon_escaped = re.escape(emoticon) # espace regexp metachars
+			emoticons_pattern += emoticon_escaped + '|'# | means or in regexp
+			if (emoticon_length != len(emoticon)):
+				# Build up expressions to match emoticons next to other emoticons
+				emoticons_pattern_prematch  = emoticons_pattern_prematch[:-1]  + ')|(?<='
+				emoticons_pattern_postmatch = emoticons_pattern_postmatch[:-1] + ')|(?='
+				emoticon_length = len(emoticon)
+			emoticons_pattern_prematch += emoticon_escaped  + '|'
+			emoticons_pattern_postmatch += emoticon_escaped + '|'
+		# We match from our list of emoticons, but they must either have
+		# whitespace, or another emoticon next to it to match successfully
+		# [\w.] alphanumeric and dot (for not matching 8) in (2.8))
+		emoticons_pattern = '|' + \
+			'(?:(?<![\w.]' + emoticons_pattern_prematch[:-1]   + '))' + \
+			'(?:'       + emoticons_pattern[:-1]            + ')'  + \
+			'(?:(?![\w.]'  + emoticons_pattern_postmatch[:-1]  + '))'
+
+	# because emoticons match later (in the string) they need to be after
+	# basic matches that may occur earlier
+	emot_and_basic_pattern = basic_pattern + emoticons_pattern
+	view.emot_and_basic_re = re.compile(emot_and_basic_pattern, re.IGNORECASE)
+
+
+def _parse_css_color(color):
+	'''_parse_css_color(css_color) -> gtk.gdk.Color'''
+	if color.startswith("rgb(") and color.endswith(')'):
+		r, g, b = [int(c)*257 for c in color[4:-1].split(',')]
+		return gtk.gdk.Color(r, g, b)
+	else:
+		return gtk.gdk.color_parse(color)
+	
+
+class HtmlHandler(xml.sax.handler.ContentHandler):
+	
+	def __init__(self, textview, startiter):
+		xml.sax.handler.ContentHandler.__init__(self)
+		self.textbuf = textview.get_buffer()
+		self.textview = textview
+		self.iter = startiter
+		self.text = ''
+		self.starting=True
+		self.preserve = False
+		self.styles = [] # a gtk.TextTag or None, for each span level
+		self.list_counters = [] # stack (top at head) of list
+								# counters, or None for unordered list
+
+	def _parse_style_color(self, tag, value):
+		color = _parse_css_color(value)
+		tag.set_property("foreground-gdk", color)
+
+	def _parse_style_background_color(self, tag, value):
+		color = _parse_css_color(value)
+		tag.set_property("background-gdk", color)
+		if gtk.gtk_version >= (2, 8):
+			tag.set_property("paragraph-background-gdk", color)
+
+
+	if gtk.gtk_version >= (2, 8, 5) or gobject.pygtk_version >= (2, 8, 1):
+
+		def _get_current_attributes(self):
+			attrs = self.textview.get_default_attributes()
+			self.iter.backward_char()
+			self.iter.get_attributes(attrs)
+			self.iter.forward_char()
+			return attrs
+		
+	else:
+		
+		## Workaround http://bugzilla.gnome.org/show_bug.cgi?id=317455
+		def _get_current_style_attr(self, propname, comb_oper=None):
+			tags = [tag for tag in self.styles if tag is not None]
+			tags.reverse()
+			is_set_name = propname + "-set"
+			value = None
+			for tag in tags:
+				if tag.get_property(is_set_name):
+					if value is None:
+						value = tag.get_property(propname)
+						if comb_oper is None:
+							return value
+					else:
+						value = comb_oper(value, tag.get_property(propname))
+			return value
+
+		class _FakeAttrs(object):
+			__slots__ = ("font", "font_scale")
+
+		def _get_current_attributes(self):
+			attrs = self._FakeAttrs()
+			attrs.font_scale = self._get_current_style_attr("scale",
+															operator.mul)
+			if attrs.font_scale is None:
+				attrs.font_scale = 1.0
+			attrs.font = self._get_current_style_attr("font-desc")
+			if attrs.font is None:
+				attrs.font = self.textview.style.font_desc
+			return attrs
+
+
+	def __parse_length_frac_size_allocate(self, textview, allocation,
+										  frac, callback, args):
+		callback(allocation.width*frac, *args)
+
+	def _parse_length(self, value, font_relative, callback, *args):
+		'''Parse/calc length, converting to pixels, calls callback(length, *args)
+		when the length is first computed or changes'''
+		if value.endswith('%'):
+			frac = float(value[:-1])/100
+			if font_relative:
+				attrs = self._get_current_attributes()
+				font_size = attrs.font.get_size() / pango.SCALE
+				callback(frac*display_resolution*font_size, *args)
+			else:
+				## CSS says "Percentage values: refer to width of the closest
+				##           block-level ancestor"
+				## This is difficult/impossible to implement, so we use
+				## textview width instead; a reasonable approximation..
+				alloc = self.textview.get_allocation()
+				self.__parse_length_frac_size_allocate(self.textview, alloc,
+													   frac, callback, args)
+				self.textview.connect("size-allocate",
+									  self.__parse_length_frac_size_allocate,
+									  frac, callback, args)
+
+		elif value.endswith('pt'): # points
+			callback(float(value[:-2])*display_resolution, *args)
+
+		elif value.endswith('em'): # ems, the height of the element's font
+			attrs = self._get_current_attributes()
+			font_size = attrs.font.get_size() / pango.SCALE
+			callback(float(value[:-2])*display_resolution*font_size, *args)
+
+		elif value.endswith('ex'): # x-height, ~ the height of the letter 'x'
+			## FIXME: figure out how to calculate this correctly
+			##        for now 'em' size is used as approximation
+			attrs = self._get_current_attributes()
+			font_size = attrs.font.get_size() / pango.SCALE
+			callback(float(value[:-2])*display_resolution*font_size, *args)
+
+		elif value.endswith('px'): # pixels
+			callback(int(value[:-2]), *args)
+
+		else:
+			warnings.warn("Unable to parse length value '%s'" % value)
+		
+	def __parse_font_size_cb(length, tag):
+		tag.set_property("size-points", length/display_resolution)
+	__parse_font_size_cb = staticmethod(__parse_font_size_cb)
+
+	def _parse_style_display(self, tag, value):
+		if value == 'none':
+			tag.set_property('invisible','true')
+		#Fixme: display: block, inline
+
+	def _parse_style_font_size(self, tag, value):
+		try:
+			scale = {
+				"xx-small": pango.SCALE_XX_SMALL,
+				"x-small": pango.SCALE_X_SMALL,
+				"small": pango.SCALE_SMALL,
+				"medium": pango.SCALE_MEDIUM,
+				"large": pango.SCALE_LARGE,
+				"x-large": pango.SCALE_X_LARGE,
+				"xx-large": pango.SCALE_XX_LARGE,
+				} [value]
+		except KeyError:
+			pass
+		else:
+			attrs = self._get_current_attributes()
+			tag.set_property("scale", scale / attrs.font_scale)
+			return
+		if value == 'smaller':
+			tag.set_property("scale", pango.SCALE_SMALL)
+			return
+		if value == 'larger':
+			tag.set_property("scale", pango.SCALE_LARGE)
+			return
+		self._parse_length(value, True, self.__parse_font_size_cb, tag)
+
+	def _parse_style_font_style(self, tag, value):
+		try:
+			style = {
+				"normal": pango.STYLE_NORMAL,
+				"italic": pango.STYLE_ITALIC,
+				"oblique": pango.STYLE_OBLIQUE,
+				} [value]
+		except KeyError:
+			warnings.warn("unknown font-style %s" % value)
+		else:
+			tag.set_property("style", style)
+
+	def __frac_length_tag_cb(self,length, tag, propname):
+		styles = self._get_style_tags()
+		if styles:
+			length += styles[-1].get_property(propname)
+		tag.set_property(propname, length)
+	#__frac_length_tag_cb = staticmethod(__frac_length_tag_cb)
+		
+	def _parse_style_margin_left(self, tag, value):
+		self._parse_length(value, False, self.__frac_length_tag_cb,
+						   tag, "left-margin")
+
+	def _parse_style_margin_right(self, tag, value):
+		self._parse_length(value, False, self.__frac_length_tag_cb,
+						   tag, "right-margin")
+
+	def _parse_style_font_weight(self, tag, value):
+		## TODO: missing 'bolder' and 'lighter'
+		try:
+			weight = {
+				'100': pango.WEIGHT_ULTRALIGHT,
+				'200': pango.WEIGHT_ULTRALIGHT,
+				'300': pango.WEIGHT_LIGHT,
+				'400': pango.WEIGHT_NORMAL,
+				'500': pango.WEIGHT_NORMAL,
+				'600': pango.WEIGHT_BOLD,
+				'700': pango.WEIGHT_BOLD,
+				'800': pango.WEIGHT_ULTRABOLD,
+				'900': pango.WEIGHT_HEAVY,
+				'normal': pango.WEIGHT_NORMAL,
+				'bold': pango.WEIGHT_BOLD,
+				} [value]
+		except KeyError:
+			warnings.warn("unknown font-style %s" % value)
+		else:
+			tag.set_property("weight", weight)
+
+	def _parse_style_font_family(self, tag, value):
+		tag.set_property("family", value)
+
+	def _parse_style_text_align(self, tag, value):
+		try:
+			align = {
+				'left': gtk.JUSTIFY_LEFT,
+				'right': gtk.JUSTIFY_RIGHT,
+				'center': gtk.JUSTIFY_CENTER,
+				'justify': gtk.JUSTIFY_FILL,
+				} [value]
+		except KeyError:
+			warnings.warn("Invalid text-align:%s requested" % value)
+		else:
+			tag.set_property("justification", align)
+	
+	def _parse_style_text_decoration(self, tag, value):
+		if value == "none":
+			tag.set_property("underline", pango.UNDERLINE_NONE)
+			tag.set_property("strikethrough", False)
+		elif value == "underline":
+			tag.set_property("underline", pango.UNDERLINE_SINGLE)
+			tag.set_property("strikethrough", False)
+		elif value == "overline":
+			warnings.warn("text-decoration:overline not implemented")
+			tag.set_property("underline", pango.UNDERLINE_NONE)
+			tag.set_property("strikethrough", False)
+		elif value == "line-through":
+			tag.set_property("underline", pango.UNDERLINE_NONE)
+			tag.set_property("strikethrough", True)
+		elif value == "blink":
+			warnings.warn("text-decoration:blink not implemented")
+		else:
+			warnings.warn("text-decoration:%s not implemented" % value)
+	
+	def _parse_style_white_space(self, tag, value):
+		if value == 'pre':
+			tag.set_property("wrap_mode", gtk.WRAP_NONE)
+		elif value == 'normal':
+			tag.set_property("wrap_mode", gtk.WRAP_WORD)
+		elif value == 'nowrap':
+			tag.set_property("wrap_mode", gtk.WRAP_NONE)
+	 
+	
+	## build a dictionary mapping styles to methods, for greater speed
+	__style_methods = dict()
+	for style in ["background-color", "color", "font-family", "font-size",
+				  "font-style", "font-weight", "margin-left", "margin-right",
+				  "text-align", "text-decoration", "white-space", 'display' ]:
+		try:
+			method = locals()["_parse_style_%s" % style.replace('-', '_')]
+		except KeyError:
+			warnings.warn("Style attribute '%s' not yet implemented" % style)
+		else:
+			__style_methods[style] = method
+	del style
+	## --
+
+	def _get_style_tags(self):
+		return [tag for tag in self.styles if tag is not None]
+
+	def _create_url(self, href, title, type_, id_):
+		tag = self.textbuf.create_tag(id_)
+		if href and href[0] != '#':
+			tag.href = href
+			tag.type_ = type_ # to be used by the URL handler
+			tag.connect('event', self.textview.html_hyperlink_handler, 'url', href)
+			tag.set_property('foreground', '#0000ff')
+			tag.set_property('underline', pango.UNDERLINE_SINGLE)
+			tag.is_anchor = True
+		if title:
+			tag.title = title
+		return tag
+
+
+	def _begin_span(self, style, tag=None, id_=None):
+		if style is None:
+			self.styles.append(tag)
+			return None
+		if tag is None:
+			if id_:
+				tag = self.textbuf.create_tag(id_)
+			else:
+				tag = self.textbuf.create_tag()
+		for attr, val in [item.split(':', 1) for item in style.split(';') if len(item.strip())]:
+			attr = attr.strip().lower()
+			val = val.strip()
+			try:
+				method = self.__style_methods[attr]
+			except KeyError:
+				warnings.warn("Style attribute '%s' requested "
+							  "but not yet implemented" % attr)
+			else:
+				method(self, tag, val)
+		self.styles.append(tag)
+
+	def _end_span(self):
+		self.styles.pop()
+
+	def _jump_line(self):
+		self.textbuf.insert_with_tags_by_name(self.iter, '\n', 'eol')
+		self.starting = True
+
+	def _insert_text(self, text):
+		if self.starting and text != '\n':
+			self.starting = (text[-1] == '\n')
+		tags = self._get_style_tags()
+		if tags:
+			self.textbuf.insert_with_tags(self.iter, text, *tags)
+		else:
+			self.textbuf.insert(self.iter, text)
+
+	def _starts_line(self):
+		return self.starting or self.iter.starts_line()
+		
+	def _flush_text(self):
+		if not self.text: return
+		text, self.text = self.text, ''
+		if not self.preserve:
+			text = text.replace('\n', ' ')
+			self.handle_specials(whitespace_rx.sub(' ', text))
+		else:
+			self._insert_text(text.strip("\n"))
+
+	def _anchor_event(self, tag, textview, event, iter, href, type_):
+		if event.type == gtk.gdk.BUTTON_PRESS:
+			self.textview.emit("url-clicked", href, type_)
+			return True
+		return False
+
+	def handle_specials(self, text):
+		index = 0
+		se = self.textview.config.get('show_ascii_formatting_chars')
+		if self.textview.config.get('emoticons_theme'):
+			iterator = self.textview.emot_and_basic_re.finditer(text)
+		else:
+			iterator = self.textview.basic_pattern_re.finditer(text)
+		for match in iterator:
+			start, end = match.span()
+			special_text = text[start:end]
+			if start != 0:
+				self._insert_text(text[index:start])
+			index = end # update index
+			#emoticons
+			possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
+			if self.textview.config.get('emoticons_theme') and \
+					possible_emot_ascii_caps in self.textview.interface.emoticons.keys():
+				#it's an emoticon
+				emot_ascii = possible_emot_ascii_caps
+				anchor = self.textbuf.create_child_anchor(self.iter)
+				img = gtk.Image()
+				img.set_from_file(self.textview.interface.emoticons[emot_ascii])
+				# TODO: add alt/tooltip with the special_text (a11y) 
+				self.textview.add_child_at_anchor(img, anchor)
+				img.show()
+			else:
+				# now print it
+				if special_text.startswith('/'): # it's explicit italics
+					self.startElement('i', {})
+				elif special_text.startswith('_'): # it's explicit underline
+					self.startElement("u", {})
+				if se: self._insert_text(special_text[0])
+				self.handle_specials(special_text[1:-1])
+				if se: self._insert_text(special_text[0])
+				if special_text.startswith('_'): # it's explicit underline
+					self.endElement('u')
+				if special_text.startswith('/'): # it's explicit italics
+					self.endElement('i')
+		self._insert_text(text[index:])
+		
+	def characters(self, content):
+		if self.preserve:
+			self.text += content
+			return
+		if allwhitespace_rx.match(content) is not None and self._starts_line():
+			return
+		self.text += content
+		#if self.text: self.text += ' '
+		#self.handle_specials(whitespace_rx.sub(' ', content))
+		if allwhitespace_rx.match(self.text) is not None and self._starts_line():
+			self.text = ''
+		#self._flush_text()
+
+
+	def startElement(self, name, attrs):
+		self._flush_text()
+		klass = [i for i in attrs.get('class',' ').split(' ') if i]
+		style = attrs.get('style','')
+		#Add styles defined for classes
+		#TODO: priority between class and style elements?
+		for k in klass:
+			if k  in classes:
+				style += classes[k]
+
+		tag = None
+		#FIXME: if we want to use id, it needs to be unique across
+		# the whole textview, so we need to add something like the
+		# message-id to it.
+		#id_ = attrs.get('id',None) 
+		id_ = None
+		if name == 'a':
+            #TODO: accesskey, charset, hreflang, rel, rev, tabindex, type
+			href = attrs.get('href', None)
+			title = attrs.get('title', attrs.get('rel',href))
+			type_ = attrs.get('type', None)
+			tag = self._create_url(href, title, type_, id_)
+		elif name == 'blockquote':
+			cite = attrs.get('cite', None)
+			if cite:
+				tag = self.textbuf.create_tag(id_)
+				tag.title = title
+				tag.is_anchor = True
+		elif name in LIST_ELEMS:
+			style += ';margin-left: 2em'
+		if name in element_styles:
+			style += element_styles[name]
+
+		if style == '':
+			style = None        
+		self._begin_span(style, tag, id_)
+
+		if name == 'br':
+			pass # handled in endElement
+		elif name == 'hr':
+			pass # handled in endElement
+		elif name in BLOCK:
+			if not self._starts_line():
+				self._jump_line()
+			if name == 'pre':
+				self.preserve = True
+		elif name == 'span':
+			pass
+		elif name in ('dl', 'ul'):
+			if not self._starts_line():
+				self._jump_line()
+			self.list_counters.append(None)
+		elif name == 'ol':
+			if not self._starts_line():
+				self._jump_line()
+			self.list_counters.append(0)
+		elif name == 'li':
+			if self.list_counters[-1] is None:
+				li_head = unichr(0x2022)
+			else:
+				self.list_counters[-1] += 1
+				li_head = "%i." % self.list_counters[-1]
+			self.text = ' '*len(self.list_counters)*4 + li_head + ' '
+			self._flush_text()
+			self.starting = True
+		elif name == 'dd':
+			self._jump_line()
+		elif name == 'dt':
+			if not self.starting:
+				self._jump_line()
+		elif name == 'img':
+			try:
+				## Max image size = 10 MB (to try to prevent DoS)
+				mem = urllib2.urlopen(attrs['src']).read(10*1024*1024)
+				## Caveat: GdkPixbuf is known not to be safe to load
+				## images from network... this program is now potentially
+				## hackable ;)
+				loader = gtk.gdk.PixbufLoader()
+				loader.write(mem); loader.close()
+				pixbuf = loader.get_pixbuf()
+			except Exception, ex:
+				gajim.log.debug(str('Error loading image'+ex))
+				pixbuf = None
+				alt = attrs.get('alt', "Broken image")
+				try:
+					loader.close()
+				except: pass
+			if pixbuf is not None:
+				tags = self._get_style_tags()
+				if tags:
+					tmpmark = self.textbuf.create_mark(None, self.iter, True)
+
+				self.textbuf.insert_pixbuf(self.iter, pixbuf)
+
+				if tags:
+					start = self.textbuf.get_iter_at_mark(tmpmark)
+					for tag in tags:
+						self.textbuf.apply_tag(tag, start, self.iter)
+					self.textbuf.delete_mark(tmpmark)
+			else:
+				self._insert_text("[IMG: %s]" % alt)
+		elif name == 'body' or name == 'html':
+			pass
+		elif name == 'a':
+			pass
+		elif name in INLINE:
+			pass
+		else:
+			warnings.warn("Unhandled element '%s'" % name)
+
+	def endElement(self, name):
+		endPreserving = False
+		newLine = False
+		if name == 'br':
+			newLine = True
+		elif name == 'hr':
+			#FIXME: plenty of unused attributes (width, height,...) :)
+			self._jump_line()
+			try:
+				self.textbuf.insert_pixbuf(self.iter, self.textview.focus_out_line_pixbuf)
+				#self._insert_text(u"\u2550"*40)
+				self._jump_line()
+			except Exception, e:
+				log.debug(str("Error in hr"+e))
+		elif name in LIST_ELEMS:
+			self.list_counters.pop()
+		elif name == 'li':
+			newLine = True
+		elif name == 'img':
+			pass
+		elif name == 'body' or name == 'html':
+			pass
+		elif name == 'a':
+			pass
+		elif name in INLINE:
+			pass
+		elif name in ('dd', 'dt', ):
+			pass
+		elif name in BLOCK:
+			if name == 'pre':
+				endPreserving = True
+		else:
+			warnings.warn("Unhandled element '%s'" % name)
+		self._flush_text()
+		if endPreserving:
+			self.preserve = False
+		if newLine:
+			self._jump_line()
+		self._end_span()
+		#if not self._starts_line():
+		#    self.text = ' '
+
+class HtmlTextView(gtk.TextView):
+	__gtype_name__ = 'HtmlTextView'
+	__gsignals__ = {
+		'url-clicked': (gobject.SIGNAL_RUN_LAST, None, (str, str)), # href, type
+	}
+	
+	def __init__(self):
+		gobject.GObject.__init__(self)
+		self.set_wrap_mode(gtk.WRAP_CHAR)
+		self.set_editable(False)
+		self._changed_cursor = False
+		self.connect("motion-notify-event", self.__motion_notify_event)
+		self.connect("leave-notify-event", self.__leave_event)
+		self.connect("enter-notify-event", self.__motion_notify_event)
+		self.get_buffer().create_tag('eol', scale = pango.SCALE_XX_SMALL)
+		self.tooltip = tooltips.BaseTooltip()
+		# needed to avoid bootstrapping problems
+		from common import gajim
+		self.config = gajim.config
+		self.interface = gajim.interface
+		self.log = gajim.log
+		# end big hack
+		build_patterns(self,gajim.config,gajim.interface)
+
+	def __leave_event(self, widget, event):
+		if self._changed_cursor:
+			window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+			window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
+			self._changed_cursor = False
+
+	def show_tooltip(self, tag):
+		if not self.tooltip.win:
+			# check if the current pointer is still over the line
+			text = getattr(tag, 'title', False)
+			if text:
+				pointer = self.get_pointer()
+				position = self.window.get_origin()
+				win = self.get_toplevel()
+				self.tooltip.show_tooltip(text, 8, position[1] + pointer[1])
+
+	def __motion_notify_event(self, widget, event):
+		x, y, _ = widget.window.get_pointer()
+		x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
+		tags = widget.get_iter_at_location(x, y).get_tags()
+		is_over_anchor = False
+		for tag in tags:
+			if getattr(tag, 'is_anchor', False):
+				is_over_anchor = True
+				break
+		if self.tooltip.timeout != 0:
+			# Check if we should hide the line tooltip
+			if not is_over_anchor:
+				self.tooltip.hide_tooltip()
+		if not self._changed_cursor and is_over_anchor:
+			window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+			window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
+			self._changed_cursor = True
+			gobject.timeout_add(500,
+								self.show_tooltip, tag)
+		elif self._changed_cursor and not is_over_anchor:
+			window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+			window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
+			self._changed_cursor = False
+		return False
+
+	def display_html(self, html):
+		buffer = self.get_buffer()
+		eob = buffer.get_end_iter()
+		## this works too if libxml2 is not available
+		# parser = xml.sax.make_parser(['drv_libxml2'])
+		# parser.setFeature(xml.sax.handler.feature_validation, True)
+		parser = xml.sax.make_parser()
+		parser.setContentHandler(HtmlHandler(self, eob))
+		parser.parse(StringIO(html))
+		
+		#if not eob.starts_line():
+		#    buffer.insert(eob, "\n")
+
+if gobject.pygtk_version < (2, 8):
+	gobject.type_register(HtmlTextView)
+
+change_cursor = None
+
+if __name__ == '__main__':
+
+	htmlview = HtmlTextView()
+
+	tooltip = tooltips.BaseTooltip()
+	def on_textview_motion_notify_event(widget, event):
+		'''change the cursor to a hand when we are over a mail or an url'''
+		global change_cursor
+		pointer_x, pointer_y, spam = htmlview.window.get_pointer()
+		x, y = htmlview.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
+								   pointer_y)
+		tags = htmlview.get_iter_at_location(x, y).get_tags()
+		if change_cursor:
+			htmlview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+					 gtk.gdk.Cursor(gtk.gdk.XTERM))
+			change_cursor = None
+		tag_table = htmlview.get_buffer().get_tag_table()
+		over_line = False
+		for tag in tags:
+			try:
+				if tag.is_anchor:
+					htmlview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+										gtk.gdk.Cursor(gtk.gdk.HAND2))
+					change_cursor = tag
+				elif tag == tag_table.lookup('focus-out-line'):
+					over_line = True
+			except: pass
+
+		#if line_tooltip.timeout != 0:
+			# Check if we should hide the line tooltip
+		#	if not over_line:
+		#		line_tooltip.hide_tooltip()
+		#if over_line and not line_tooltip.win:
+		#	line_tooltip.timeout = gobject.timeout_add(500,
+		#		show_line_tooltip)
+		#	htmlview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
+		#		gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
+		#	change_cursor = tag
+
+	htmlview.connect('motion_notify_event', on_textview_motion_notify_event)
+
+	def handler(texttag, widget, event, iter, kind, href):
+		if event.type == gtk.gdk.BUTTON_PRESS:
+			print href
+
+	htmlview.html_hyperlink_handler = handler
+
+	htmlview.display_html('<div><span style="color: red; text-decoration:underline">Hello</span><br/>\n'
+						  '  <img src="http://images.slashdot.org/topics/topicsoftware.gif"/><br/>\n'
+						  '  <span style="font-size: 500%; font-family: serif">World</span>\n'
+						  '</div>\n')
+	htmlview.display_html("<hr />")
+	htmlview.display_html("""
+	  <p style='font-size:large'>
+		<span style='font-style: italic'>O<span style='font-size:larger'>M</span>G</span>, 
+		I&apos;m <span style='color:green'>green</span>
+		with <span style='font-weight: bold'>envy</span>!
+	  </p>
+		""")
+	htmlview.display_html("<hr />")
+	htmlview.display_html("""
+	<body xmlns='http://www.w3.org/1999/xhtml'>
+	  <p>As Emerson said in his essay <span style='font-style: italic; background-color:cyan'>Self-Reliance</span>:</p>
+	  <p style='margin-left: 5px; margin-right: 2%'>
+		&quot;A foolish consistency is the hobgoblin of little minds.&quot;
+	  </p>
+	</body>
+		""")
+	htmlview.display_html("<hr />")
+	htmlview.display_html("""
+	<body xmlns='http://www.w3.org/1999/xhtml'>
+	  <p style='text-align:center'>Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>?</p>
+	  <p style='text-align:right'><img src='http://www.jabber.org/images/psa-license.jpg'
+			  alt='A License to Jabber'
+			  height='261'
+			  width='537'/></p>
+	</body>
+		""")
+	htmlview.display_html("<hr />")
+	htmlview.display_html("""
+	<body xmlns='http://www.w3.org/1999/xhtml'>
+	  <ul style='background-color:rgb(120,140,100)'>
+	   <li> One </li>
+	   <li> Two </li>
+	   <li> Three </li>
+	  </ul><hr /><pre style="background-color:rgb(120,120,120)">def fac(n):
+  def faciter(n,acc): 
+	if n==0: return acc
+	return faciter(n-1, acc*n)
+  if n&lt;0: raise ValueError("Must be non-negative")
+  return faciter(n,1)</pre>
+	</body>
+		""")
+	htmlview.display_html("<hr />")
+	htmlview.display_html("""
+	<body xmlns='http://www.w3.org/1999/xhtml'>
+	 <ol style='background-color:rgb(120,140,100)'>
+	   <li> One </li>
+	   <li> Two is nested: <ul style='background-color:rgb(200,200,100)'>
+			 <li> One </li>
+			 <li> Two </li>
+			 <li> Three </li>
+			</ul></li>
+	   <li> Three </li></ol>
+	</body>
+		""")
+	htmlview.show()
+	sw = gtk.ScrolledWindow()
+	sw.set_property("hscrollbar-policy", gtk.POLICY_AUTOMATIC)
+	sw.set_property("vscrollbar-policy", gtk.POLICY_AUTOMATIC)
+	sw.set_property("border-width", 0)
+	sw.add(htmlview)
+	sw.show()
+	frame = gtk.Frame()
+	frame.set_shadow_type(gtk.SHADOW_IN)
+	frame.show()
+	frame.add(sw)
+	w = gtk.Window()
+	w.add(frame)
+	w.set_default_size(400, 300)
+	w.show_all()
+	w.connect("destroy", lambda w: gtk.main_quit())
+	gtk.main()
diff --git a/src/roster_window.py b/src/roster_window.py
index b2268e9210441a1c41a88cf0397b3868ca71b094..2f5f08083abe6212672114bca1e95ae2971d1268 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -2531,7 +2531,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 
 	def on_message(self, jid, msg, tim, account, encrypted = False,
 			msg_type = '', subject = None, resource = '', msg_id = None,
-			user_nick = '', advanced_notif_num = None):
+			user_nick = '', advanced_notif_num = None, xhtml = None):
 		'''when we receive a message'''
 		contact = None
 		# if chat window will be for specific resource
@@ -2599,7 +2599,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 			if msg_type == 'error':
 				typ = 'status'
 			ctrl.print_conversation(msg, typ, tim = tim, encrypted = encrypted,
-						subject = subject)
+						subject = subject, xhtml = xhtml)
 			if msg_id:
 				gajim.logger.set_read_messages([msg_id])
 			return
@@ -2613,7 +2613,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
 		show_in_roster = notify.get_show_in_roster(event_type, account, contact)
 		show_in_systray = notify.get_show_in_systray(event_type, account, contact)
 		event = gajim.events.create_event(type_, (msg, subject, msg_type, tim,
-			encrypted, resource, msg_id), show_in_roster = show_in_roster,
+			encrypted, resource, msg_id, xhtml), show_in_roster = show_in_roster,
 			show_in_systray = show_in_systray)
 		gajim.events.add_event(account, fjid, event)
 		if popup: