[Santiago Gala] we can now see XHTML (JEP 0071). See #316

This commit is contained in:
Yann Leboulanger 2006-10-03 14:12:42 +00:00
parent aeb6116ba6
commit 6b40b5ad32
9 changed files with 1202 additions and 132 deletions

View File

@ -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()

View File

@ -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):

View File

@ -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

View File

@ -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)

View File

@ -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')):

View File

@ -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

View File

@ -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)

968
src/htmltextview.py Normal file
View File

@ -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()

View File

@ -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: