XEP-0308 Last Message Correction support.
This commit is contained in:
parent
c6c3f2636b
commit
f0a4094cc9
8 changed files with 307 additions and 75 deletions
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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_)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Reference in a new issue