XEP-0308 Last Message Correction support.

This commit is contained in:
Yann Leboulanger 2013-04-25 20:38:18 +02:00
parent c6c3f2636b
commit f0a4094cc9
8 changed files with 307 additions and 75 deletions

View File

@ -344,13 +344,13 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if resource is None:
# We very likely got a contact with a random resource.
# This is bad, we need the highest for caps etc.
c = gajim.contacts.get_contact_with_highest_priority(
acct, contact.jid)
c = gajim.contacts.get_contact_with_highest_priority(acct,
contact.jid)
if c and not isinstance(c, GC_Contact):
contact = c
MessageControl.__init__(self, type_id, parent_win, widget_name,
contact, acct, resource=resource)
contact, acct, resource=resource)
widget = self.xml.get_object('history_button')
# set document-open-recent icon for history button
@ -369,15 +369,15 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
# Create banner and connect signals
widget = self.xml.get_object('banner_eventbox')
id_ = widget.connect('button-press-event',
self._on_banner_eventbox_button_press_event)
self._on_banner_eventbox_button_press_event)
self.handlers[id_] = widget
self.urlfinder = re.compile(
r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
self.banner_status_label = self.xml.get_object('banner_label')
id_ = self.banner_status_label.connect('populate_popup',
self.on_banner_label_populate_popup)
self.on_banner_label_populate_popup)
self.handlers[id_] = self.banner_status_label
# Init DND
@ -397,34 +397,38 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
id_ = self.conv_textview.connect('quote', self.on_quote)
self.handlers[id_] = self.conv_textview.tv
id_ = self.conv_textview.tv.connect('key_press_event',
self._conv_textview_key_press_event)
self._conv_textview_key_press_event)
self.handlers[id_] = self.conv_textview.tv
# FIXME: DND on non editable TextView, find a better way
self.drag_entered = False
id_ = self.conv_textview.tv.connect('drag_data_received',
self._on_drag_data_received)
self._on_drag_data_received)
self.handlers[id_] = self.conv_textview.tv
id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion)
self.handlers[id_] = self.conv_textview.tv
id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave)
self.handlers[id_] = self.conv_textview.tv
self.conv_textview.tv.drag_dest_set(Gtk.DestDefaults.MOTION |
Gtk.DestDefaults.HIGHLIGHT |
Gtk.DestDefaults.DROP,
self.dnd_list, Gdk.DragAction.COPY)
Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
self.dnd_list, Gdk.DragAction.COPY)
self.conv_scrolledwindow = self.xml.get_object(
'conversation_scrolledwindow')
'conversation_scrolledwindow')
self.conv_scrolledwindow.add(self.conv_textview.tv)
widget = self.conv_scrolledwindow.get_vadjustment()
id_ = widget.connect('value-changed',
self.on_conversation_vadjustment_value_changed)
self.on_conversation_vadjustment_value_changed)
self.handlers[id_] = widget
id_ = widget.connect('changed',
self.on_conversation_vadjustment_changed)
self.on_conversation_vadjustment_changed)
self.handlers[id_] = widget
self.scroll_to_end_id = None
self.was_at_the_end = True
self.correcting = False
self.last_sent_msg = None
self.last_sent_txt = None
self.last_received_txt = {} # one per name
self.last_received_id = {} # one per name
# add MessageTextView to UI and connect signals
self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
@ -928,12 +932,23 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
label = self.get_seclabel()
def _cb(msg, cb, *cb_args):
self.last_sent_msg = msg
self.last_sent_txt = cb_args[1]
if cb:
cb(msg, *cb_args)
if self.correcting and self.last_sent_msg:
correction_msg = self.last_sent_msg
else:
correction_msg = None
gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
account=self.account, jid=self.contact.jid, message=message,
keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
resource=resource, user_nick=self.user_nick, xhtml=xhtml,
label=label, callback=callback, callback_args=callback_args,
control=self, attention=attention))
label=label, callback=_cb, callback_args=[callback] + callback_args,
control=self, attention=attention, correction_msg=correction_msg))
# Record the history of sent messages
self.save_message(message, 'sent')
@ -974,9 +989,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
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, xhtml=None, simple=False,
xep0184_id=None, graphics=True, displaymarking=None, msg_id=None):
xep0184_id=None, graphics=True, displaymarking=None, msg_id=None,
correct_id=None):
"""
Print 'chat' type messages
correct_id = (message_id, correct_id)
"""
jid = self.contact.jid
full_jid = self.get_full_jid()
@ -984,16 +1001,28 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
end = False
if self.was_at_the_end or kind == 'outgoing':
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, xhtml, simple=simple, graphics=graphics,
displaymarking=displaymarking)
old_txt = ''
if name in self.last_received_txt:
old_txt = self.last_received_txt[name]
if correct_id and correct_id[1] and \
name in self.conv_textview.last_received_message_marks and \
correct_id[1] == self.last_received_id[name]:
self.conv_textview.correct_last_received_message(text, xhtml,
name, old_txt)
else:
textview.print_conversation_line(text, jid, kind, name, tim,
other_tags_for_name, other_tags_for_time, other_tags_for_text,
subject, old_kind, xhtml, simple=simple, graphics=graphics,
displaymarking=displaymarking)
if xep0184_id is not None:
textview.show_xep0184_warning(xep0184_id)
if not count_as_new:
return
if kind in ('incoming', 'outgoing'):
self.last_received_txt[name] = text
self.last_received_id[name] = correct_id[0]
if kind == 'incoming':
if not self.type_id == message_control.TYPE_GC or \
gajim.config.get('notify_on_all_muc_messages') or \
@ -1407,6 +1436,21 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
start_iter = msg_buf.get_start_iter()
end_iter = msg_buf.get_end_iter()
self.orig_msg = msg_buf.get_text(start_iter, end_iter, False)
if pos == size and size > 0 and direction == 'up' and \
msg_type == 'sent' and not self.correcting:
self.correcting = True
self.old_message_tv_color = self.msg_textview.get_style().base[
Gtk.StateType.NORMAL]
self.msg_textview.modify_base(Gtk.StateType.NORMAL, Gdk.color_parse(
'PaleGoldenrod'))
message = history[pos - 1]
msg_buf.set_text(message)
return
if self.correcting:
# We were previously correcting
self.msg_textview.modify_base(Gtk.StateType.NORMAL,
self.old_message_tv_color)
self.correcting = False
pos += -1 if direction == 'up' else +1
if pos == -1:
return
@ -1492,6 +1536,8 @@ class ChatControl(ChatControlBase):
'chat_control', contact, acct, resource)
self.gpg_is_active = False
self.last_recv_message_id = None
self.last_recv_message_marks = None
# for muc use:
# widget = self.xml.get_object('muc_window_actions_button')
self.actions_button = self.xml.get_object('message_window_actions_button')
@ -2355,7 +2401,8 @@ class ChatControl(ChatControlBase):
GObject.source_remove(self.possible_inactive_timeout_id)
self._schedule_activity_timers()
def _on_sent(id_, contact, message, encrypted, xhtml, label):
def _on_sent(msg, contact, message, encrypted, xhtml, label, old_txt):
id_ = msg.getID()
if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts',
self.account, 'request_receipt'):
xep0184_id = id_
@ -2365,13 +2412,22 @@ class ChatControl(ChatControlBase):
displaymarking = label.getTag('displaymarking')
else:
displaymarking = None
self.print_conversation(message, self.contact.jid, encrypted=encrypted,
xep0184_id=xep0184_id, xhtml=xhtml, displaymarking=displaymarking)
if self.correcting and \
self.conv_textview.last_sent_message_marks[0]:
self.conv_textview.correct_last_sent_message(message, xhtml,
self.get_our_nick(), old_txt)
self.correcting = False
self.msg_textview.modify_base(Gtk.StateType.NORMAL,
self.old_message_tv_color)
return
self.print_conversation(message, self.contact.jid,
encrypted=encrypted, xep0184_id=xep0184_id, xhtml=xhtml,
displaymarking=displaymarking)
ChatControlBase.send_message(self, message, keyID, type_='chat',
chatstate=chatstate_to_send, xhtml=xhtml, callback=_on_sent,
callback_args=[contact, message, encrypted, xhtml,
self.get_seclabel()], process_commands=process_commands,
self.get_seclabel(), self.last_sent_txt], process_commands=process_commands,
attention=attention)
def check_for_possible_paused_chatstate(self, arg):
@ -2483,7 +2539,7 @@ class ChatControl(ChatControlBase):
def print_conversation(self, text, frm='', tim=None, encrypted=False,
subject=None, xhtml=None, simple=False, xep0184_id=None,
displaymarking=None, msg_id=None):
displaymarking=None, msg_id=None, correct_id=None):
"""
Print a line in the conversation
@ -2548,7 +2604,7 @@ class ChatControl(ChatControlBase):
ChatControlBase.print_conversation_line(self, text, kind, name, tim,
subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml,
simple=simple, xep0184_id=xep0184_id, displaymarking=displaymarking,
msg_id=msg_id)
msg_id=msg_id, correct_id=correct_id)
if text.startswith('/me ') or text.startswith('/me\n'):
self.old_msg_kind = None
else:

View File

@ -254,9 +254,11 @@ class CommonConnection:
def _prepare_message(self, jid, msg, keyID, type_='chat', subject='',
chatstate=None, msg_id=None, resource=None, user_nick=None, xhtml=None,
session=None, forward_from=None, form_node=None, label=None,
original_message=None, delayed=None, attention=False, callback=None):
original_message=None, delayed=None, attention=False, correction_msg=None,
callback=None):
if not self.connection or self.connected < 2:
return 1
try:
jid = self.check_jid(jid)
except helpers.InvalidFormat:
@ -306,7 +308,7 @@ class CommonConnection:
jid, xhtml, subject, chatstate, msg_id,
label, forward_from, delayed, session,
form_node, user_nick, keyID, attention,
callback)
correction_msg, callback)
gajim.nec.push_incoming_event(GPGTrustKeyEvent(None,
conn=self, callback=_on_always_trust))
else:
@ -314,7 +316,7 @@ class CommonConnection:
original_message, fjid, resource, jid, xhtml,
subject, chatstate, msg_id, label, forward_from,
delayed, session, form_node, user_nick, keyID,
attention, callback)
attention, correction_msg, callback)
gajim.thread_interface(encrypt_thread, [msg, keyID, False],
_on_encrypted, [])
return
@ -322,18 +324,19 @@ class CommonConnection:
self._message_encrypted_cb(('', error), type_, msg, msgtxt,
original_message, fjid, resource, jid, xhtml, subject,
chatstate, msg_id, label, forward_from, delayed, session,
form_node, user_nick, keyID, attention, callback)
form_node, user_nick, keyID, attention, correction_msg,
callback)
return
self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
label, forward_from, delayed, session, form_node, user_nick,
attention, callback)
attention, correction_msg, callback)
def _message_encrypted_cb(self, output, type_, msg, msgtxt,
original_message, fjid, resource, jid, xhtml, subject, chatstate, msg_id,
label, forward_from, delayed, session, form_node, user_nick, keyID,
attention, callback):
attention, correction_msg, callback):
msgenc, error = output
if msgenc and not error:
@ -346,7 +349,7 @@ class CommonConnection:
self._on_continue_message(type_, msg, msgtxt, original_message,
fjid, resource, jid, xhtml, subject, msgenc, keyID,
chatstate, msg_id, label, forward_from, delayed, session,
form_node, user_nick, attention, callback)
form_node, user_nick, attention, correction_msg, callback)
return
# Encryption failed, do not send message
tim = localtime()
@ -356,7 +359,32 @@ class CommonConnection:
def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
label, forward_from, delayed, session, form_node, user_nick, attention,
callback):
correction_msg, callback):
if correction_msg:
id_ = correction_msg.getID()
if correction_msg.getTag('replace'):
correction_msg.delChild('replace')
correction_msg.setTag('replace', attrs={'id': id_},
namespace=nbxmpp.NS_CORRECT)
id2 = self.connection.getAnID()
correction_msg.setID(id2)
correction_msg.setBody(msgtxt)
if xhtml:
correction_msg.setXHTML(xhtml)
if session:
session.last_send = time.time()
# XEP-0200
if session.enable_encryption:
correction_msg = session.encrypt_stanza(correction_msg)
if callback:
callback(jid, msg, keyID, forward_from, session, original_message,
subject, type_, correction_msg, xhtml)
return
if type_ == 'chat':
msg_iq = nbxmpp.Message(to=fjid, body=msgtxt, typ=type_,
xhtml=xhtml)
@ -1941,8 +1969,8 @@ class Connection(CommonConnection, ConnectionHandlers):
def send_message(self, jid, msg, keyID=None, type_='chat', subject='',
chatstate=None, msg_id=None, resource=None, user_nick=None, xhtml=None,
label=None, session=None, forward_from=None, form_node=None,
original_message=None, delayed=None, attention=False, callback=None,
callback_args=[], now=False):
original_message=None, delayed=None, attention=False, correction_msg=None,
callback=None, callback_args=[], now=False):
def cb(jid, msg, keyID, forward_from, session, original_message,
subject, type_, msg_iq, xhtml):
@ -1961,7 +1989,7 @@ class Connection(CommonConnection, ConnectionHandlers):
user_nick=user_nick, xhtml=xhtml, label=label, session=session,
forward_from=forward_from, form_node=form_node,
original_message=original_message, delayed=delayed,
attention=attention, callback=cb)
attention=attention, correction_msg=correction_msg, callback=cb)
def _nec_message_outgoing(self, obj):
if obj.account != self.name:
@ -1974,7 +2002,7 @@ class Connection(CommonConnection, ConnectionHandlers):
gajim.nec.push_incoming_event(MessageSentEvent(None, conn=self,
jid=jid, message=msg, keyID=keyID, chatstate=obj.chatstate))
if obj.callback:
obj.callback(msg_id, *obj.callback_args)
obj.callback(msg_iq, *obj.callback_args)
if not obj.is_loggable:
return
@ -1986,7 +2014,8 @@ class Connection(CommonConnection, ConnectionHandlers):
resource=obj.resource, user_nick=obj.user_nick, xhtml=obj.xhtml,
label=obj.label, session=obj.session, forward_from=obj.forward_from,
form_node=obj.form_node, original_message=obj.original_message,
delayed=obj.delayed, attention=obj.attention, callback=cb)
delayed=obj.delayed, attention=obj.attention,
correction_msg=obj.correction_msg, callback=cb)
def send_contacts(self, contacts, fjid, type_='message'):
"""
@ -2524,18 +2553,38 @@ class Connection(CommonConnection, ConnectionHandlers):
t.setTagData('password', password)
self.connection.send(p)
def send_gc_message(self, jid, msg, xhtml=None, label=None):
def send_gc_message(self, jid, msg, xhtml=None, label=None,
correction_msg=None, callback=None):
if not gajim.account_is_connected(self.name):
return
if correction_msg:
id_ = correction_msg.getID()
if correction_msg.getTag('replace'):
correction_msg.delChild('replace')
correction_msg.setTag('replace', attrs={'id': id_},
namespace=nbxmpp.NS_CORRECT)
id2 = self.connection.getAnID()
correction_msg.setID(id2)
correction_msg.setBody(msg)
if xhtml:
correction_msg.setXHTML(xhtml)
self.connection.send(correction_msg)
gajim.nec.push_incoming_event(MessageSentEvent(None, conn=self,
jid=jid, message=msg, keyID=None, chatstate=None))
if callback:
callback(correction_msg, msg)
return
if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
from common.rst_xhtml_generator import create_xhtml
xhtml = create_xhtml(msg)
msg_iq = nbxmpp.Message(jid, msg, typ='groupchat', xhtml=xhtml)
if label is not None:
msg_iq.addChild(node = label)
msg_iq.addChild(node=label)
self.connection.send(msg_iq)
gajim.nec.push_incoming_event(MessageSentEvent(None, conn=self,
jid=jid, message=msg, keyID=None, chatstate=None))
if callback:
callback(msg_iq, msg)
def send_gc_subject(self, jid, subject):
if not gajim.account_is_connected(self.name):

View File

@ -1248,6 +1248,7 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.popup = False
self.msg_id = None # id in log database
self.attention = False # XEP-0224
self.correct_id = None # XEP-0308
self.receipt_request_tag = self.stanza.getTag('request',
namespace=nbxmpp.NS_RECEIPTS)
@ -1291,6 +1292,10 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.msgtxt += _('URL:')
self.msgtxt += ' ' + self.oob_url
replace = self.stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT)
if replace:
self.correct_id = replace.getAttr('id')
return True
class ChatstateReceivedEvent(nec.NetworkIncomingEvent):
@ -1318,6 +1323,7 @@ class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
self.nickname = self.msg_obj.resource
self.timestamp = self.msg_obj.timestamp
self.xhtml_msgtxt = self.stanza.getXHTML()
self.correct_id = None # XEP-0308
if gajim.config.get('ignore_incoming_xhtml'):
self.xhtml_msgtxt = None
@ -1390,6 +1396,10 @@ class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
[self.stanza, self.msg_obj], 0)
return
replace = self.stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT)
if replace:
self.correct_id = replace.getAttr('id')
return True
class GcSubjectReceivedEvent(nec.NetworkIncomingEvent):
@ -2444,6 +2454,7 @@ class MessageOutgoingEvent(nec.NetworkOutgoingEvent):
self.is_loggable = True
self.control = None
self.attention = False
self.correction_msg = None
def generate(self):
return True

View File

@ -212,7 +212,7 @@ gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
nbxmpp.NS_SSN, nbxmpp.NS_MOOD, nbxmpp.NS_ACTIVITY, nbxmpp.NS_NICK,
nbxmpp.NS_ROSTERX, nbxmpp.NS_SECLABEL, nbxmpp.NS_HASHES,
nbxmpp.NS_HASHES_MD5, nbxmpp.NS_HASHES_SHA1, nbxmpp.NS_HASHES_SHA256,
nbxmpp.NS_HASHES_SHA512]
nbxmpp.NS_HASHES_SHA512, nbxmpp.NS_CORRECT]
# Optional features gajim supports per account
gajim_optional_features = {}

View File

@ -77,7 +77,7 @@ class TextViewImage(Gtk.Image):
self._disconnect_funcs = []
self.connect('parent-set', self.on_parent_set)
self.connect('draw', self.on_expose)
self.set_tooltip_text(text)
self.set_tooltip_markup(text)
self.anchor.plaintext = text
def _get_selected(self):
@ -172,9 +172,12 @@ class ConversationTextview(GObject.GObject):
)
)
FOCUS_OUT_LINE_PIXBUF = gtkgui_helpers.get_icon_pixmap('gajim-muc_separator')
FOCUS_OUT_LINE_PIXBUF = gtkgui_helpers.get_icon_pixmap(
'gajim-muc_separator')
XEP0184_WARNING_PIXBUF = gtkgui_helpers.get_icon_pixmap(
'gajim-receipt_missing')
'gajim-receipt_missing')
MESSAGE_CORRECTED_PIXBUF = gtkgui_helpers.get_icon_pixmap(
'gajim-message_corrected')
# smooth scroll constants
MAX_SCROLL_TIME = 0.4 # seconds
@ -208,6 +211,9 @@ class ConversationTextview(GObject.GObject):
self.image_cache = {}
self.xep0184_marks = {}
self.xep0184_shown = {}
self.last_sent_message_marks = [None, None]
# A pair per occupant. Key is '' in normal chat
self.last_received_message_marks = {}
# It's True when we scroll in the code, so we can detect scroll from user
self.auto_scrolling = False
@ -463,6 +469,51 @@ class ConversationTextview(GObject.GObject):
self.smooth_id = None
self.smooth_scroll_timer.cancel()
def show_corrected_message_warning(self, iter_, text=''):
buffer_ = self.tv.get_buffer()
buffer_.begin_user_action()
buffer_.insert(iter_, ' ')
anchor = buffer_.create_child_anchor(iter_)
img = TextViewImage(anchor, text)
img.set_from_pixbuf(ConversationTextview.MESSAGE_CORRECTED_PIXBUF)
img.show()
self.tv.add_child_at_anchor(img, anchor)
buffer_.end_user_action()
def correct_last_sent_message(self, message, xhtml, name, old_txt):
m1 = self.last_sent_message_marks[0]
m2 = self.last_sent_message_marks[1]
buffer_ = self.tv.get_buffer()
i1 = buffer_.get_iter_at_mark(m1)
i2 = buffer_.get_iter_at_mark(m2)
txt = buffer_.get_text(i1, i2)
buffer_.delete(i1, i2)
i2 = self.print_real_text(message, text_tags=['outgoingtxt'], name=name,
xhtml=xhtml, iter_=i1)
tt_txt = _('<b>Message was corrected. Last message was:</b>\n %s') % \
old_txt
self.show_corrected_message_warning(i2, tt_txt)
self.last_sent_message_marks[1] = buffer_.create_mark(None, i2,
left_gravity=True)
def correct_last_received_message(self, message, xhtml, name, old_txt):
if name not in self.last_received_message_marks:
return
m1 = self.last_received_message_marks[name][0]
m2 = self.last_received_message_marks[name][1]
buffer_ = self.tv.get_buffer()
i1 = buffer_.get_iter_at_mark(m1)
i2 = buffer_.get_iter_at_mark(m2)
txt = buffer_.get_text(i1, i2)
buffer_.delete(i1, i2)
i2 = self.print_real_text(message, text_tags=['incomingtxt'], name=name,
xhtml=xhtml, iter_=i1)
tt_txt = _('<b>Message was corrected. Last message was:</b>\n %s') % \
old_txt
self.show_corrected_message_warning(i2, tt_txt)
self.last_received_message_marks[name][1] = buffer_.create_mark(None, i2,
left_gravity=True)
def show_xep0184_warning(self, id_):
if id_ in self.xep0184_marks:
return
@ -991,7 +1042,8 @@ class ConversationTextview(GObject.GObject):
else:
helpers.launch_browser_mailer(kind, word)
def detect_and_print_special_text(self, otext, other_tags, graphics=True):
def detect_and_print_special_text(self, otext, other_tags, graphics=True,
iter_=None):
"""
Detect special text (emots & links & formatting), print normal text
before any special text it founds, then print special text (that happens
@ -1025,6 +1077,10 @@ class ConversationTextview(GObject.GObject):
iterator = gajim.interface.emot_and_basic_re.finditer(otext)
else: # search for just urls + mail + formatting
iterator = gajim.interface.basic_pattern_re.finditer(otext)
if iter_:
end_iter = iter_
else:
end_iter = buffer_.get_end_iter()
for match in iterator:
start, end = match.span()
special_text = otext[start:end]
@ -1039,18 +1095,19 @@ class ConversationTextview(GObject.GObject):
index = end # update index
# now print it
self.print_special_text(special_text, other_tags, graphics=graphics)
self.print_special_text(special_text, other_tags, graphics=graphics,
iter_=end_iter)
specials_limit -= 1
if specials_limit <= 0:
break
# add the rest of text located in the index and after
end_iter = buffer_.get_end_iter()
insert_tags_func(end_iter, otext[index:], *other_tags)
return buffer_.get_end_iter()
return end_iter
def print_special_text(self, special_text, other_tags, graphics=True):
def print_special_text(self, special_text, other_tags, graphics=True,
iter_=None):
"""
Is called by detect_and_print_special_text and prints special text
(emots, links, formatting)
@ -1069,7 +1126,7 @@ class ConversationTextview(GObject.GObject):
text_is_valid_uri = False
is_xhtml_link = None
show_ascii_formatting_chars = \
gajim.config.get('show_ascii_formatting_chars')
gajim.config.get('show_ascii_formatting_chars')
buffer_ = self.tv.get_buffer()
# Detect XHTML-IM link
@ -1087,17 +1144,20 @@ class ConversationTextview(GObject.GObject):
text_is_valid_uri = True
possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
if iter_:
end_iter = iter_
else:
end_iter = buffer_.get_end_iter()
if gajim.config.get('emoticons_theme') and \
possible_emot_ascii_caps in gajim.interface.emoticons.keys() and graphics:
# it's an emoticon
emot_ascii = possible_emot_ascii_caps
end_iter = buffer_.get_end_iter()
anchor = buffer_.create_child_anchor(end_iter)
img = TextViewImage(anchor, special_text)
animations = gajim.interface.emoticons_animations
if not emot_ascii in animations:
animations[emot_ascii] = GdkPixbuf.PixbufAnimation.new_from_file(
gajim.interface.emoticons[emot_ascii])
gajim.interface.emoticons[emot_ascii])
img.set_from_animation(animations[emot_ascii])
img.show()
self.images.append(img)
@ -1108,13 +1168,13 @@ class ConversationTextview(GObject.GObject):
text_is_valid_uri and not is_xhtml_link:
tags.append('url')
elif special_text.startswith('mailto:') and not is_xhtml_link:
tags.append('mail')
tags.append('mail')
elif special_text.startswith('xmpp:') and not is_xhtml_link:
tags.append('xmpp')
tags.append('xmpp')
elif gajim.interface.sth_at_sth_dot_sth_re.match(special_text) and\
not is_xhtml_link:
# it's a JID or mail
tags.append('sth_at_sth')
# it's a JID or mail
tags.append('sth_at_sth')
elif special_text.startswith('*'): # it's a bold text
tags.append('bold')
if special_text[1] == '/' and special_text[-2] == '/' and\
@ -1163,7 +1223,6 @@ class ConversationTextview(GObject.GObject):
else:
# It's nothing special
if use_other_tags:
end_iter = buffer_.get_end_iter()
insert_tags_func = buffer_.insert_with_tags_by_name
if other_tags and isinstance(other_tags[0], Gtk.TextTag):
insert_tags_func = buffer_.insert_with_tags
@ -1173,7 +1232,6 @@ class ConversationTextview(GObject.GObject):
buffer_.insert(end_iter, special_text)
if tags:
end_iter = buffer_.get_end_iter()
all_tags = tags[:]
if use_other_tags:
all_tags += other_tags
@ -1183,7 +1241,6 @@ class ConversationTextview(GObject.GObject):
if 'url' in tags:
puny_text = puny_encode(special_text).decode('utf-8')
if not puny_text.endswith('-'):
end_iter = buffer_.get_end_iter()
buffer_.insert(end_iter, " (%s)" % puny_text)
def print_empty_line(self):
@ -1193,9 +1250,9 @@ class ConversationTextview(GObject.GObject):
self.just_cleared = False
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, xhtml=None,
simple=False, graphics=True, displaymarking=None):
other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[],
subject=None, old_kind=None, xhtml=None, simple=False, graphics=True,
displaymarking=None):
"""
Print 'chat' type messages
"""
@ -1276,6 +1333,7 @@ class ConversationTextview(GObject.GObject):
kind = 'status'
other_text_tag = self.detect_other_text_tag(text, kind)
text_tags = other_tags_for_text[:] # create a new list
mark1 = None
if other_text_tag:
# note that color of /me may be overwritten in gc_control
text_tags.append(other_text_tag)
@ -1292,11 +1350,21 @@ class ConversationTextview(GObject.GObject):
direction_mark=direction_mark)
if kind == 'incoming':
text_tags.append('incomingtxt')
mark1 = buffer_.create_mark(None, buffer_.get_end_iter(),
left_gravity=True)
elif kind == 'outgoing':
text_tags.append('outgoingtxt')
mark1 = buffer_.create_mark(None, buffer_.get_end_iter(),
left_gravity=True)
self.print_subject(subject)
self.print_real_text(text, text_tags, name, xhtml, graphics=graphics)
if mark1:
mark2 = buffer_.create_mark(None, buffer_.get_end_iter(),
left_gravity=True)
if kind == 'incoming':
self.last_received_message_marks[name] = [mark1, mark2]
elif kind == 'outgoing':
self.last_sent_message_marks = [mark1, mark2]
# scroll to the end of the textview
if at_the_end or kind == 'outgoing':
# we are at the end or we are sending something
@ -1367,16 +1435,19 @@ class ConversationTextview(GObject.GObject):
format_ = direction_mark + before_str + name + after_str + ' '
buffer_.insert_with_tags_by_name(end_iter, format_, *name_tags)
def print_subject(self, subject):
def print_subject(self, subject, iter_=None):
if subject: # if we have subject, show it too!
subject = _('Subject: %s\n') % subject
buffer_ = self.tv.get_buffer()
end_iter = buffer_.get_end_iter()
if iter_:
end_iter = iter_
else:
end_iter = buffer_.get_end_iter()
buffer_.insert(end_iter, subject)
self.print_empty_line()
def print_real_text(self, text, text_tags=[], name=None, xhtml=None,
graphics=True):
graphics=True, iter_=None):
"""
Add normal and special text. call this to add text
"""
@ -1395,4 +1466,5 @@ class ConversationTextview(GObject.GObject):
text = '* ' + name + text[3:]
text_tags.append('italic')
# detect urls formatting and if the user has it on emoticons
self.detect_and_print_special_text(text, text_tags, graphics=graphics)
return self.detect_and_print_special_text(text, text_tags, graphics=graphics,
iter_=iter_)

View File

@ -1012,9 +1012,26 @@ class GroupchatControl(ChatControlBase):
tim=obj.timestamp, xhtml=None,
displaymarking=obj.displaymarking)
else:
if obj.nick in self.last_received_txt and obj.correct_id and \
obj.correct_id == self.last_received_id[obj.nick]:
if obj.nick == self.nick:
old_txt = self.last_sent_txt
self.last_sent_txt = obj.msgtxt
self.conv_textview.correct_last_sent_message(obj.msgtxt,
obj.xhtml_msgtxt, obj.nick, old_txt)
else:
old_txt = self.last_received_txt[obj.nick]
self.conv_textview.correct_last_received_message(obj.msgtxt,
obj.xhtml_msgtxt, obj.nick, old_txt)
self.last_received_txt[obj.nick] = obj.msgtxt
self.last_received_id[obj.nick] = obj.stanza.getID()
return
if obj.nick == self.nick:
self.last_sent_txt = obj.msgtxt
self.print_conversation(obj.msgtxt, contact=obj.nick,
tim=obj.timestamp, xhtml=obj.xhtml_msgtxt,
displaymarking=obj.displaymarking)
displaymarking=obj.displaymarking,
correct_id=(obj.stanza.getID(), None))
obj.needs_highlight = self.needs_visual_notification(obj.msgtxt)
def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None,
@ -1086,7 +1103,7 @@ class GroupchatControl(ChatControlBase):
displaymarking=displaymarking)
def print_conversation(self, text, contact='', tim=None, xhtml=None,
graphics=True, displaymarking=None):
graphics=True, displaymarking=None, correct_id=None):
"""
Print a line in the conversation
@ -1147,7 +1164,8 @@ class GroupchatControl(ChatControlBase):
ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
other_tags_for_name, [], other_tags_for_text, xhtml=xhtml,
graphics=graphics, displaymarking=displaymarking)
graphics=graphics, displaymarking=displaymarking,
correct_id=correct_id)
def get_nb_unread(self):
type_events = ['printed_marked_gc_msg']
@ -1590,6 +1608,18 @@ class GroupchatControl(ChatControlBase):
else:
s = _('%(nick)s is now known as %(new_nick)s') % {
'nick': obj.nick, 'new_nick': obj.new_nick}
tv = self.conv_textview
if obj.nick in tv.last_received_message_marks:
tv.last_received_message_marks[obj.new_nick] = \
tv.last_received_message_marks[obj.nick]
del tv.last_received_message_marks[obj.nick]
if obj.nick in self.last_received_txt:
self.last_received_txt[obj.new_nick] = \
self.last_received_txt[obj.nick]
del self.last_received_txt[obj.nick]
self.last_received_id[obj.new_nick] = \
self.last_received_id[obj.nick]
del self.last_received_id[obj.nick]
# We add new nick to muc roster here, so we don't see
# that "new_nick has joined the room" when he just changed
# nick.
@ -1889,9 +1919,23 @@ class GroupchatControl(ChatControlBase):
if message != '' or message != '\n':
self.save_message(message, 'sent')
def _cb(msg, msg_txt):
# we'll save sent message text when we'll receive it in
# _nec_gc_message_received
self.last_sent_msg = msg
if self.correcting:
self.correcting = False
self.msg_textview.modify_base(Gtk.StateType.NORMAL,
self.old_message_tv_color)
if self.correcting and self.last_sent_msg:
correction_msg = self.last_sent_msg
else:
correction_msg = None
# Send the message
gajim.connections[self.account].send_gc_message(self.room_jid,
message, xhtml=xhtml, label=label)
message, xhtml=xhtml, label=label,
correction_msg=correction_msg, callback=_cb)
self.msg_textview.get_buffer().set_text('')
self.msg_textview.grab_focus()

View File

@ -2719,7 +2719,7 @@ class RosterWindow:
obj.session.control.print_conversation(obj.msgtxt, typ,
tim=obj.timestamp, encrypted=obj.encrypted, subject=obj.subject,
xhtml=obj.xhtml, displaymarking=obj.displaymarking,
msg_id=obj.msg_id)
msg_id=obj.msg_id, correct_id=(obj.id_, obj.correct_id))
if obj.msg_id:
pw = obj.session.control.parent_win
end = obj.session.control.was_at_the_end

View File

@ -99,7 +99,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
sectext = _('The database file (%s) cannot be read. Try to '
'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) '
'or remove it (all history will be lost).') % \
common.logger.LOG_DB_PATH
gajim.logger.LOG_DB_PATH
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self.conn, level='error', pri_txt=pritext,
sec_txt=sectext))