From 39216af2deb62d88d890f79fdd8752155506a464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Fri, 9 Dec 2016 12:14:14 +0100 Subject: [PATCH] print_conversation_line() refactoring It now orders all messages with the given timestamp this also includes inserting messages at the correct point The timestamp precision for incoming and outgoing messages was raised to include fractions of a second --- src/chat_control.py | 56 +-- src/common/connection_handlers.py | 2 +- src/common/connection_handlers_events.py | 14 +- src/common/logger.py | 10 +- src/conversation_textview.py | 484 +++++++++++++---------- src/groupchat_control.py | 66 +--- src/roster_window.py | 4 +- 7 files changed, 327 insertions(+), 309 deletions(-) diff --git a/src/chat_control.py b/src/chat_control.py index 747a3a67d..5db5fb75a 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -345,7 +345,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): 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 @@ -793,7 +792,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): def _cb(obj, msg, cb, *cb_args): self.last_sent_msg = msg - self.last_sent_txt = cb_args[0] if cb: cb(obj, msg, *cb_args) @@ -849,7 +847,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): 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_log_id=None, - correct_id=None, additional_data={}): + msg_stanza_id=None, correct_id=None, additional_data={}): """ Print 'chat' type messages correct_id = (message_id, correct_id) @@ -860,26 +858,12 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): end = False if self.was_at_the_end or kind == 'outgoing': end = True - 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) - elif correct_id and correct_id[1] and \ - self.conv_textview.last_sent_message_marks[0] and \ - correct_id[1] == self.last_received_id[name]: - # this is for carbon copied messages that are sent from another - # resource - self.conv_textview.correct_last_sent_message(text, xhtml, - self.get_our_nick(), old_txt, additional_data=additional_data) - 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, additional_data=additional_data) + + 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, msg_stanza_id=msg_stanza_id, + correct_id=correct_id, additional_data=additional_data) if xep0184_id is not None: textview.show_xep0184_warning(xep0184_id) @@ -1411,6 +1395,7 @@ class ChatControl(ChatControlBase): self.gpg_is_active = False self.last_recv_message_id = None self.last_recv_message_marks = None + self.last_message_timestamp = None # for muc use: # widget = self.xml.get_object('muc_window_actions_button') self.actions_button = self.xml.get_object('message_window_actions_button') @@ -2329,7 +2314,7 @@ class ChatControl(ChatControlBase): GLib.source_remove(self.possible_inactive_timeout_id) self._schedule_activity_timers() - def _on_sent(obj, msg_stanza, message, encrypted, xhtml, label, old_txt): + def _on_sent(obj, msg_stanza, message, encrypted, xhtml, label): id_ = msg_stanza.getID() if self.contact.supports(NS_RECEIPTS) and gajim.config.get_per( 'accounts', self.account, 'request_receipt'): @@ -2340,22 +2325,21 @@ class ChatControl(ChatControlBase): displaymarking = label.getTag('displaymarking') else: displaymarking = None - 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, additional_data=obj.additional_data) + if self.correcting: self.correcting = False self.msg_textview.override_background_color( 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, additional_data=obj.additional_data) + displaymarking=displaymarking, msg_stanza_id=id_, + correct_id=msg_stanza.getTagAttr('replace', 'id'), + additional_data=obj.additional_data) ChatControlBase.send_message(self, message, keyID, type_='chat', chatstate=chatstate_to_send, xhtml=xhtml, callback=_on_sent, - callback_args=[message, encrypted, xhtml, self.get_seclabel(), - self.last_sent_txt], process_commands=process_commands, + callback_args=[message, encrypted, xhtml, self.get_seclabel()], + process_commands=process_commands, attention=attention) def check_for_possible_paused_chatstate(self, arg): @@ -2469,7 +2453,8 @@ 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_log_id=None, correct_id=None, additional_data={}): + displaymarking=None, msg_log_id=None, correct_id=None, + msg_stanza_id=None, additional_data={}): """ Print a line in the conversation @@ -2534,7 +2519,8 @@ 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_log_id=msg_log_id, correct_id=correct_id, additional_data=additional_data) + msg_log_id=msg_log_id, msg_stanza_id=msg_stanza_id, + correct_id=correct_id, additional_data=additional_data) if text.startswith('/me ') or text.startswith('/me\n'): self.old_msg_kind = None else: @@ -3008,7 +2994,7 @@ class ChatControl(ChatControlBase): kind = 'status' name = self.contact.get_shown_name() - tim = time.localtime(float(row[0])) + tim = float(row[0]) if gajim.config.get('restored_messages_small'): small_attr = ['small'] diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index c9af40b91..361bf32c9 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -1032,7 +1032,7 @@ class ConnectionHandlersBase: # Do not override assigned key obj.contact.keyID = obj.keyID if obj.timestamp: - obj.contact.last_status_time = obj.timestamp + obj.contact.last_status_time = localtime(obj.timestamp) elif not gajim.block_signed_in_notifications[account]: # We're connected since more that 30 seconds obj.contact.last_status_time = localtime() diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py index 60840f96f..e8cbe7557 100644 --- a/src/common/connection_handlers_events.py +++ b/src/common/connection_handlers_events.py @@ -94,12 +94,16 @@ class HelperEvent: self.gc_control = minimized.get(self.jid) def _generate_timestamp(self, tag): + # Make sure we use only int/float Epoch time + if not isinstance(tag, str): + self.timestamp = time_time() + return try: tim = helpers.datetime_tuple(tag) + self.timestamp = timegm(tim) except Exception: log.error('wrong timestamp, ignoring it: ' + tag) - tim = localtime() - self.timestamp = localtime(timegm(tim)) + self.timestamp = time_time() def get_chatstate(self): """ @@ -1059,7 +1063,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): return tim = delay.getAttr('stamp') tim = helpers.datetime_tuple(tim) - self.tim = localtime(timegm(tim)) + self.tim = timegm(tim) to_ = self.msg_.getAttr('to') if to_: to_ = gajim.get_jid_without_resource(to_) @@ -1278,7 +1282,7 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): if not form.getField('FORM_TYPE'): return - + if form['FORM_TYPE'] == 'urn:xmpp:ssn': self.session.handle_negotiation(form) else: @@ -1302,6 +1306,7 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self._generate_timestamp(self.stanza.getTimestamp()) + self.encrypted = False xep_200_encrypted = self.stanza.getTag('c', namespace=nbxmpp.NS_STANZA_CRYPTO) @@ -1522,6 +1527,7 @@ class GcMessageReceivedEvent(nec.NetworkIncomingEvent): self.stanza = self.msg_obj.stanza if not hasattr(self, 'additional_data'): self.additional_data = self.msg_obj.additional_data + self.id_ = self.msg_obj.stanza.getID() self.fjid = self.msg_obj.fjid self.msgtxt = self.msg_obj.msgtxt self.jid = self.msg_obj.jid diff --git a/src/common/logger.py b/src/common/logger.py index f36f924c8..f4f035dd2 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -523,9 +523,9 @@ class Logger: subject_col = subject additional_data_col = json.dumps(additional_data) if tim: - time_col = int(float(time.mktime(tim))) + time_col = float(tim) else: - time_col = int(float(time.time())) + time_col = float(time.time()) kind_col, show_col = self.convert_human_values_to_db_api_values(kind, show) @@ -1112,9 +1112,9 @@ class Logger: def save_if_not_exists(self, with_, direction, tim, msg='', nick=None, additional_data={}): if tim: - time_col = int(float(time.mktime(tim))) + time_col = float(tim) else: - time_col = int(float(time.time())) + time_col = float(time.time()) if not msg: return if self.jid_is_from_pm(with_) or nick: @@ -1157,7 +1157,7 @@ class Logger: self.write(type_, with_, message=msg, tim=tim, additional_data=additional_data) def _nec_gc_message_received(self, obj): - tim_f = float(time.mktime(obj.timestamp)) + tim_f = float(obj.timestamp) tim_int = int(tim_f) if gajim.config.should_log(obj.conn.name, obj.jid) and not \ tim_int < obj.conn.last_history_time[obj.jid] and obj.msgtxt and \ diff --git a/src/conversation_textview.py b/src/conversation_textview.py index cdaedd47b..40e864ad4 100644 --- a/src/conversation_textview.py +++ b/src/conversation_textview.py @@ -39,7 +39,6 @@ import time import os import tooltips import dialogs -import locale import queue import urllib @@ -58,6 +57,9 @@ NOT_SHOWN = 0 ALREADY_RECEIVED = 1 SHOWN = 2 +import logging +log = logging.getLogger('gajim.conversation_textview') + def is_selection_modified(mark): name = mark.get_name() if name and name in ('selection_bound', 'insert'): @@ -155,7 +157,6 @@ class TextViewImage(Gtk.Image): if is_selection_modified(mark): self._update_selected() - class ConversationTextview(GObject.GObject): """ Class for the conversation textview (where user reads already said messages) @@ -186,14 +187,17 @@ class ConversationTextview(GObject.GObject): """ GObject.GObject.__init__(self) self.used_in_history_window = used_in_history_window - + self.line = 0 + self.message_list = [] + self.corrected_text_list = {} self.fc = FuzzyClock() - # no need to inherit TextView, use it as atrribute is safer self.tv = HtmlTextView() self.tv.hyperlink_handler = self.hyperlink_handler + self.tv.set_has_tooltip(True) + # set properties self.tv.set_border_width(1) self.tv.set_accepts_tab(True) @@ -207,9 +211,10 @@ 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 = {} + # self.last_sent_message_id = msg_stanza_id + self.last_sent_message_id = None + # last_received_message_id[name] = (msg_stanza_id, line_start_mark) + self.last_received_message_id = {} # It's True when we scroll in the code, so we can detect scroll from user self.auto_scrolling = False @@ -226,6 +231,8 @@ class ConversationTextview(GObject.GObject): id_ = self.tv.connect('draw', self.on_textview_draw) self.handlers[id_] = self.tv + id_ = self.tv.connect('query-tooltip', self.query_tooltip) + self.handlers[id_] = self.tv self.account = account @@ -327,18 +334,39 @@ class ConversationTextview(GObject.GObject): self.xep0184_warning_tooltip = tooltips.BaseTooltip() - self.line_tooltip = tooltips.BaseTooltip() self.smooth_id = None self.just_cleared = False + def query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip): + x_pos, y_pos = self.tv.window_to_buffer_coords( + Gtk.TextWindowType.TEXT, x_pos, y_pos) + if Gtk.MINOR_VERSION > 18: + iter_ = self.tv.get_iter_at_position(x_pos, y_pos)[1] + else: + iter_ = self.tv.get_iter_at_position(x_pos, y_pos)[0] + for tag in iter_.get_tags(): + tag_name = tag.get_property('name') + if tag_name == 'focus-out-line': + tooltip.set_text(_( + 'Text below this line is what has ' + 'been said since the\nlast time you paid attention to this ' + 'group chat')) + return True + + try: + text = self.corrected_text_list[tag_name] + tooltip.set_markup(text) + return True + except KeyError: + pass + return False + def del_handlers(self): for i in self.handlers.keys(): if self.handlers[i].handler_is_connected(i): self.handlers[i].disconnect(i) del self.handlers self.tv.destroy() - #FIXME: - # self.line_tooltip.destroy() def update_tags(self): self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor')) @@ -399,7 +427,7 @@ class ConversationTextview(GObject.GObject): self.auto_scrolling = False def smooth_scroll_to_end(self): - if None != self.smooth_id: # already scrolling + if self.smooth_id is not None: # already scrolling return False self.smooth_id = GLib.timeout_add(self.SCROLL_DELAY, self.smooth_scroll) @@ -449,55 +477,46 @@ 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_message(self, correct_id, kind, name): + allowed = True + if kind == 'incoming': + try: + if correct_id in self.last_received_message_id[name]: + start_mark = self.last_received_message_id[name][1] + else: + allowed = False + except KeyError: + allowed = False + elif kind == 'outgoing': + if self.last_sent_message_id[0] == correct_id: + start_mark = self.last_sent_message_id[1] + else: + allowed = False + else: + allowed = False - def correct_last_sent_message(self, message, xhtml, name, old_txt, additional_data={}): - 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, True) - buffer_.delete(i1, i2) - tag = 'outgoingtxt' - if message.startswith('/me'): - tag = 'outgoing' - i2 = self.print_conversation_line(message, '', 'outgoing', name, None, - xhtml=xhtml, iter_=i1, additional_data=additional_data) - tt_txt = _('Message was corrected. Last message was:\n %s') % \ - GLib.markup_escape_text(old_txt) - self.show_corrected_message_warning(i2, tt_txt) - self.last_sent_message_marks[1] = buffer_.create_mark(None, i2, - left_gravity=True) + if not allowed: + log.debug('Message correctiong not allowed') + return None + + end_mark, index = self.get_end_mark(correct_id, start_mark) + if not index: + log.debug('Could not find line to correct') + return None - def correct_last_received_message(self, message, xhtml, name, old_txt, - other_tags_for_name=[], other_tags_for_text=[], additional_data={}): - 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, True) - buffer_.delete(i1, i2) - i2 = self.print_conversation_line(message, '', 'incoming', name, None, - other_tags_for_name=other_tags_for_name, - other_tags_for_text=other_tags_for_text, xhtml=xhtml, iter_=i1, additional_data=additional_data) - tt_txt = _('Message was corrected. Last message was:\n %s') % \ - GLib.markup_escape_text(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) + if not end_mark: + end_iter = self.tv.get_buffer().get_end_iter() + else: + end_iter = buffer_.get_iter_at_mark(end_mark) + + start_iter = buffer_.get_iter_at_mark(start_mark) + + old_txt = buffer_.get_text(start_iter, end_iter, True) + buffer_.delete(start_iter, end_iter) + buffer_.delete_mark(start_mark) + + return index, end_mark, old_txt def show_xep0184_warning(self, id_): if id_ in self.xep0184_marks: @@ -640,30 +659,6 @@ class ConversationTextview(GObject.GObject): "end. If this icon stays\nfor a long time, it's likely the " 'message got lost.'), 8, position[1] + y) - def show_line_tooltip(self): - self.line_tooltip.timeout = 0 - w = self.tv.get_window(Gtk.TextWindowType.TEXT) - device = w.get_display().get_device_manager().get_client_pointer() - pointer = w.get_device_position(device) - x = pointer[1] - y = pointer[2] - iter_ = self.tv.get_iter_at_location(x, y) - if isinstance(iter_, tuple): - iter_ = iter_[1] - tags = iter_.get_tags() - tag_table = self.tv.get_buffer().get_tag_table() - over_line = False - for tag in tags: - if tag == tag_table.lookup('focus-out-line'): - over_line = True - break - if over_line and not self.line_tooltip.win: - # check if the current pointer is still over the line - position = w.get_origin()[1:] - self.line_tooltip.show_tooltip(_('Text below this line is what has ' - 'been said since the\nlast time you paid attention to this ' - 'group chat'), 8, position[1] + y) - def on_textview_draw(self, widget, ctx): return #TODO @@ -709,7 +704,6 @@ class ConversationTextview(GObject.GObject): w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.XTERM)) self.change_cursor = False tag_table = self.tv.get_buffer().get_tag_table() - over_line = False xep0184_warning = False for tag in tags: @@ -717,25 +711,15 @@ class ConversationTextview(GObject.GObject): tag_table.lookup('xmpp'), tag_table.lookup('sth_at_sth')): w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND2)) self.change_cursor = True - elif tag == tag_table.lookup('focus-out-line'): - over_line = True elif tag == tag_table.lookup('xep0184-warning'): xep0184_warning = True - if self.line_tooltip.timeout != 0 or self.line_tooltip.shown: - # Check if we should hide the line tooltip - if not over_line: - self.line_tooltip.hide_tooltip() if self.xep0184_warning_tooltip.timeout != 0 or \ self.xep0184_warning_tooltip.shown: # Check if we should hide the XEP-184 warning tooltip if not xep0184_warning: self.xep0184_warning_tooltip.hide_tooltip() - if over_line and not self.line_tooltip.win: - self.line_tooltip.timeout = GLib.timeout_add(500, - self.show_line_tooltip) - w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)) - self.change_cursor = True + if xep0184_warning and not self.xep0184_warning_tooltip.win: self.xep0184_warning_tooltip.timeout = GLib.timeout_add(500, self.show_xep0184_warning_tooltip) @@ -1243,62 +1227,105 @@ class ConversationTextview(GObject.GObject): buffer_.insert_with_tags_by_name(iter_, '\n', 'eol') self.just_cleared = False + def get_end_mark(self, msg_stanza_id, start_mark): + for index, msg in enumerate(self.message_list): + if msg[2] == msg_stanza_id and msg[1] == start_mark: + try: + end_mark = self.message_list[index + 1][1] + end_mark_name = end_mark.get_name() + except IndexError: + # We are at the last message + end_mark = None + end_mark_name = None + + log.debug('start mark: %s, end mark: %s, ' + 'replace message-list index: %s', + start_mark.get_name(), end_mark_name, index) + + return end_mark, index + log.debug('stanza-id not in message list') + return None, None + + def get_insert_mark(self, timestamp): + # message_list = [(timestamp, line_start_mark, msg_stanza_id)] + # We check if this is a new Message + try: + if self.message_list[-1][0] <= timestamp: + return None, None + except IndexError: + # We have no Messages in the TextView + return None, None + + # Not a new Message + # Search for insertion point + for index, msg in enumerate(self.message_list): + if msg[0] > timestamp: + return msg[1], index + + # Should not happen, but who knows + return None, None + def print_conversation_line(self, text, jid, kind, name, tim, - other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[], + other_tags_for_name=None, other_tags_for_time=None, other_tags_for_text=None, subject=None, old_kind=None, xhtml=None, simple=False, graphics=True, - displaymarking=None, iter_=None, additional_data={}): + displaymarking=None, msg_stanza_id=None, correct_id=None, additional_data=None): """ Print 'chat' type messages """ buffer_ = self.tv.get_buffer() buffer_.begin_user_action() - if iter_: - temp_mark = buffer_.create_mark(None, iter_, left_gravity=True) - if self.marks_queue.full(): - # remove oldest line - m1 = self.marks_queue.get() - m2 = self.marks_queue.get() - i1 = buffer_.get_iter_at_mark(m1) - i2 = buffer_.get_iter_at_mark(m2) - buffer_.delete(i1, i2) - buffer_.delete_mark(m1) - if iter_: - end_iter = buffer_.get_iter_at_mark(temp_mark) - buffer_.delete_mark(temp_mark) - else: - end_iter = buffer_.get_end_iter() - end_offset = end_iter.get_offset() - at_the_end = self.at_the_end() - move_selection = False - if buffer_.get_has_selection() and buffer_.get_selection_bounds()[1].\ - get_offset() == end_offset: - move_selection = True + insert_mark = None + insert_mark_name = None - if not iter_: - # Create one mark and add it to queue once if it's the first line - # else twice (one for end bound, one for start bound) - mark = None - if buffer_.get_char_count() > 0: - if not simple and not iter_: - buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol') - if move_selection: - sel_start, sel_end = buffer_.get_selection_bounds() - sel_end.backward_char() - buffer_.select_range(sel_start, sel_end) - mark = buffer_.create_mark(None, end_iter, left_gravity=True) - self.marks_queue.put(mark) - if not mark: - mark = buffer_.create_mark(None, end_iter, left_gravity=True) - self.marks_queue.put(mark) if kind == 'incoming_queue': kind = 'incoming' if old_kind == 'incoming_queue': old_kind = 'incoming' - # print the time stamp + if not tim: - # We don't have tim for outgoing messages... - tim = time.localtime() - current_print_time = gajim.config.get('print_time') + # For outgoing Messages and Status prints + tim = time.time() + + corrected = False + if correct_id: + try: + index, insert_mark, old_txt = \ + self.correct_message(correct_id, kind, name) + self.corrected_text_list[msg_stanza_id] = \ + 'Message was corrected. Last message was:\n{}' \ + .format(GLib.markup_escape_text(old_txt)) + corrected = True + except TypeError: + log.debug('Message was not corrected !') + + if not corrected: + # Get insertion point into TextView + insert_mark, index = self.get_insert_mark(tim) + + if insert_mark: + insert_mark_name = insert_mark.get_name() + + log.debug( + 'Printed Line: %s, %s, %s, inserted after: %s' + ', stanza-id: %s, correct-id: %s', + self.line, text, tim, insert_mark_name, + msg_stanza_id, correct_id) + + if not insert_mark: # Texview is empty or Message is new + iter_ = buffer_.get_end_iter() + # Insert new Line if Textview is not empty + if buffer_.get_char_count() > 0 and not corrected: + buffer_.insert_with_tags_by_name(iter_, '\n', 'eol') + else: + iter_ = buffer_.get_iter_at_mark(insert_mark) + + # Create a temporary mark at the start of the line + # with gravity=Left, so it will not move + # even if we insert directly at the mark iter + temp_mark = buffer_.create_mark('temp', iter_, left_gravity=True) + + at_the_end = self.at_the_end() + if text.startswith('/me '): direction_mark = i18n.paragraph_direction_mark(str(text[3:])) else: @@ -1306,90 +1333,96 @@ class ConversationTextview(GObject.GObject): # don't apply direction mark if it's status message if kind == 'status': direction_mark = i18n.direction_mark - if current_print_time == 'always' and kind != 'info' and not simple: - timestamp_str = self.get_time_to_show(tim, direction_mark) - timestamp = time.strftime(timestamp_str, tim) - timestamp = direction_mark + timestamp + direction_mark - if other_tags_for_time: - buffer_.insert_with_tags_by_name(end_iter, timestamp, - *other_tags_for_time) - else: - buffer_.insert (end_iter, timestamp) - elif current_print_time == 'sometimes' and kind != 'info' and not simple: - every_foo_seconds = 60 * gajim.config.get( - 'print_ichat_every_foo_minutes') - seconds_passed = time.mktime(tim) - self.last_time_printout - if seconds_passed > every_foo_seconds: - self.last_time_printout = time.mktime(tim) - end_iter = buffer_.get_end_iter() - if gajim.config.get('print_time_fuzzy') > 0: - tim_format = self.fc.fuzzy_time(gajim.config.get('print_time_fuzzy'), tim) - else: - tim_format = self.get_time_to_show(tim, direction_mark) - buffer_.insert_with_tags_by_name(end_iter, tim_format + '\n', - 'time_sometimes') + + # print the time stamp + self.print_time(text, kind, tim, simple, direction_mark, + other_tags_for_time, iter_) + # If there's a displaymarking, print it here. if displaymarking: - self.print_displaymarking(displaymarking, iter_=end_iter) + self.print_displaymarking(displaymarking, iter_=iter_) + # kind = info, we print things as if it was a status: same color, ... if kind in ('error', 'info'): kind = 'status' other_text_tag = self.detect_other_text_tag(text, kind) - text_tags = other_tags_for_text[:] # create a new list - mark1 = None + text_tags = [] + if other_tags_for_text: + text_tags = other_tags_for_text[:] # create a new list if other_text_tag: - # note that color of /me may be overwritten in gc_control text_tags.append(other_text_tag) - if text.startswith('/me') and not iter_: - mark1 = mark - else: # not status nor /me + + else: # not status nor /me if gajim.config.get('chat_merge_consecutive_nickname'): if kind != old_kind or self.just_cleared: self.print_name(name, kind, other_tags_for_name, - direction_mark=direction_mark, iter_=end_iter) + direction_mark=direction_mark, iter_=iter_) else: self.print_real_text(gajim.config.get( 'chat_merge_consecutive_nickname_indent'), - iter_=end_iter, additional_data=additional_data) + mark=insert_mark, additional_data=additional_data) else: self.print_name(name, kind, other_tags_for_name, - direction_mark=direction_mark, iter_=end_iter) + direction_mark=direction_mark, iter_=iter_) if kind == 'incoming': text_tags.append('incomingtxt') - if not iter_: - mark1 = mark elif kind == 'outgoing': text_tags.append('outgoingtxt') - if not iter_: - mark1 = mark - self.print_subject(subject, iter_=end_iter) - self.print_real_text(text, text_tags, name, xhtml, graphics=graphics, - iter_=end_iter, additional_data=additional_data) - if not iter_ and mark1: - mark2 = buffer_.create_mark(None, buffer_.get_end_iter(), - left_gravity=True) - if kind == 'incoming': - if name in self.last_received_message_marks: - m = self.last_received_message_marks[name][1] - buffer_.delete_mark(m) - self.last_received_message_marks[name] = [mark1, mark2] - elif kind == 'outgoing': - m = self.last_sent_message_marks[1] - if m: - buffer_.delete_mark(m) - 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 - # scroll to the end (via idle in case the scrollbar has appeared) - if gajim.config.get('use_smooth_scrolling'): - GLib.idle_add(self.smooth_scroll_to_end) - else: - GLib.idle_add(self.scroll_to_end) + + self.print_subject(subject, iter_=iter_) + + iter_ = self.print_real_text(text, text_tags, name, xhtml, graphics=graphics, + mark=insert_mark, additional_data=additional_data) + + if corrected: + # Show Correction Icon + buffer_.create_tag(tag_name=msg_stanza_id) + buffer_.insert(iter_, ' ') + buffer_.insert_pixbuf( + iter_, ConversationTextview.MESSAGE_CORRECTED_PIXBUF) + tag_start_iter = iter_.copy() + tag_start_iter.backward_chars(2) + buffer_.apply_tag_by_name(msg_stanza_id, tag_start_iter, iter_) + + # If we inserted a Line we add a new line at the end + if insert_mark: + buffer_.insert_with_tags_by_name(iter_, '\n', 'eol') + # We delete the temp mark and replace it with a mark + # that has gravity=right + temp_iter = buffer_.get_iter_at_mark(temp_mark) + buffer_.delete_mark(temp_mark) + new_mark = buffer_.create_mark( + str(self.line), temp_iter, left_gravity=False) + + if not index: + # New Message + self.message_list.append((tim, new_mark, msg_stanza_id)) + elif corrected: + # Replace the corrected message + self.message_list[index] = (tim, new_mark, msg_stanza_id) + else: + # We insert the message at index + self.message_list.insert(index, (tim, new_mark, msg_stanza_id)) + + if kind == 'incoming': + self.last_received_message_id[name] = (msg_stanza_id, new_mark) + elif kind == 'outgoing': + self.last_sent_message_id = (msg_stanza_id, new_mark) + + if not insert_mark: + if at_the_end or kind == 'outgoing': + # we are at the end or we are sending something + # scroll to the end (via idle in case the scrollbar has appeared) + if gajim.config.get('use_smooth_scrolling'): + GLib.idle_add(self.smooth_scroll_to_end) + else: + GLib.idle_add(self.scroll_to_end) self.just_cleared = False buffer_.end_user_action() - return end_iter + + self.line += 1 + return iter_ def get_time_to_show(self, tim, direction_mark=''): """ @@ -1428,6 +1461,34 @@ class ConversationTextview(GObject.GObject): elif text.startswith('/me ') or text.startswith('/me\n'): return kind + def print_time(self, text, kind, tim, simple, direction_mark, other_tags_for_time, iter_): + local_tim = time.localtime(tim) + buffer_ = self.tv.get_buffer() + current_print_time = gajim.config.get('print_time') + + if current_print_time == 'always' and kind != 'info' and not simple: + timestamp_str = self.get_time_to_show(local_tim, direction_mark) + timestamp = time.strftime(timestamp_str, local_tim) + timestamp = direction_mark + timestamp + direction_mark + if other_tags_for_time: + buffer_.insert_with_tags_by_name(iter_, timestamp, + *other_tags_for_time) + else: + buffer_.insert(iter_, timestamp) + elif current_print_time == 'sometimes' and kind != 'info' and not simple: + every_foo_seconds = 60 * gajim.config.get( + 'print_ichat_every_foo_minutes') + seconds_passed = tim - self.last_time_printout + if seconds_passed > every_foo_seconds: + self.last_time_printout = tim + if gajim.config.get('print_time_fuzzy') > 0: + tim_format = self.fc.fuzzy_time( + gajim.config.get('print_time_fuzzy'), tim) + else: + tim_format = self.get_time_to_show(local_tim, direction_mark) + buffer_.insert_with_tags_by_name(iter_, tim_format + '\n', + 'time_sometimes') + def print_displaymarking(self, displaymarking, iter_=None): bgcolor = displaymarking.getAttr('bgcolor') or '#FFF' fgcolor = displaymarking.getAttr('fgcolor') or '#000' @@ -1447,12 +1508,15 @@ class ConversationTextview(GObject.GObject): def print_name(self, name, kind, other_tags_for_name, direction_mark='', iter_=None): if name: + name_tags = [] buffer_ = self.tv.get_buffer() if iter_: end_iter = iter_ else: end_iter = buffer_.get_end_iter() - name_tags = other_tags_for_name[:] # create a new list + + if other_tags_for_name: + name_tags = other_tags_for_name[:] # create a new list name_tags.append(kind) before_str = gajim.config.get('before_nickname') before_str = helpers.from_one_line(before_str) @@ -1473,10 +1537,15 @@ class ConversationTextview(GObject.GObject): self.print_empty_line(end_iter) def print_real_text(self, text, text_tags=[], name=None, xhtml=None, - graphics=True, iter_=None, additional_data={}): + graphics=True, mark=None, additional_data={}): """ Add normal and special text. call this to add text """ + buffer_ = self.tv.get_buffer() + if not mark: + iter_ = buffer_.get_end_iter() + else: + iter_ = buffer_.get_iter_at_mark(mark) if xhtml: try: if name and (text.startswith('/me ') or text.startswith('/me\n')): @@ -1491,16 +1560,23 @@ class ConversationTextview(GObject.GObject): if name and (text.startswith('/me ') or text.startswith('/me\n')): text = '* ' + name + text[3:] text_tags.append('italic') - + # PluginSystem: adding GUI extension point for ConversationTextview self.plugin_modified = False gajim.plugin_manager.gui_extension_point('print_real_text', self, text, text_tags, graphics, iter_, additional_data) + if self.plugin_modified: - return self.tv.get_buffer().get_end_iter() - #needed, if buffer is manipulated by plugins without setting plugin_modified to True - iter_ = self.tv.get_buffer().get_end_iter() - + if not mark: + return buffer_.get_end_iter() + else: + return buffer_.get_iter_at_mark(mark) + + if not mark: + iter_ = buffer_.get_end_iter() + else: + iter_ = buffer_.get_iter_at_mark(mark) + # detect urls formatting and if the user has it on emoticons return self.detect_and_print_special_text(text, text_tags, graphics=graphics, iter_=iter_, additional_data=additional_data) diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 1dd535172..fa3f445d1 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -1069,64 +1069,14 @@ class GroupchatControl(ChatControlBase): # Like that xhtml messages are grayed too. self.print_old_conversation(obj.msgtxt, contact=obj.nick, tim=obj.timestamp, xhtml=None, - displaymarking=obj.displaymarking) + displaymarking=obj.displaymarking, msg_stanza_id=obj.id_) 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] - (highlight, sound) = self.highlighting_for_message(obj.msgtxt, obj.timestamp) - other_tags_for_name = [] - other_tags_for_text = [] - if obj.nick in self.gc_custom_colors: - other_tags_for_name.append('gc_nickname_color_' + \ - str(self.gc_custom_colors[obj.nick])) - else: - self.gc_count_nicknames_colors += 1 - if self.gc_count_nicknames_colors == \ - self.number_of_colors: - self.gc_count_nicknames_colors = 0 - self.gc_custom_colors[obj.nick] = \ - self.gc_count_nicknames_colors - other_tags_for_name.append('gc_nickname_color_' + \ - str(self.gc_count_nicknames_colors)) - if highlight: - # muc-specific chatstate - if self.parent_win: - self.parent_win.redraw_tab(self, 'attention') - else: - self.attention_flag = True - other_tags_for_name.append('bold') - other_tags_for_text.append('marked') - - if obj.nick in self.attention_list: - self.attention_list.remove(obj.nick) - elif len(self.attention_list) > 6: - self.attention_list.pop(0) # remove older - self.attention_list.append(obj.nick) - - if obj.msgtxt.startswith('/me ') or \ - obj.msgtxt.startswith('/me\n'): - other_tags_for_text.append('gc_nickname_color_' + \ - str(self.gc_custom_colors[obj.nick])) - self.conv_textview.correct_last_received_message( - obj.msgtxt, obj.xhtml_msgtxt, obj.nick, old_txt, - other_tags_for_name=other_tags_for_name, - other_tags_for_text=other_tags_for_text) - 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, - correct_id=(obj.stanza.getID(), None)) + correct_id=obj.correct_id, msg_stanza_id=obj.id_) obj.needs_highlight = self.needs_visual_notification(obj.msgtxt) def on_private_message(self, nick, msg, tim, xhtml, session, msg_log_id=None, @@ -1181,7 +1131,7 @@ class GroupchatControl(ChatControlBase): return None def print_old_conversation(self, text, contact='', tim=None, xhtml = None, - displaymarking=None): + displaymarking=None, msg_stanza_id=None): if contact: if contact == self.nick: # it's us kind = 'outgoing' @@ -1196,10 +1146,10 @@ class GroupchatControl(ChatControlBase): ChatControlBase.print_conversation_line(self, text, kind, contact, tim, small_attr, small_attr + ['restored_message'], small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml, - displaymarking=displaymarking) + displaymarking=displaymarking, msg_stanza_id=msg_stanza_id) def print_conversation(self, text, contact='', tim=None, xhtml=None, - graphics=True, displaymarking=None, correct_id=None): + graphics=True, displaymarking=None, correct_id=None, msg_stanza_id=None): """ Print a line in the conversation @@ -1261,7 +1211,7 @@ 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, - correct_id=correct_id) + correct_id=correct_id, msg_stanza_id=msg_stanza_id) def get_nb_unread(self): type_events = ['printed_marked_gc_msg'] @@ -1422,8 +1372,8 @@ class GroupchatControl(ChatControlBase): # print if a control is open obj.session.control.print_conversation(obj.msgtxt, tim=obj.timestamp, xhtml=obj.xhtml, encrypted=obj.encrypted, - displaymarking=obj.displaymarking, correct_id=(obj.id_, - obj.correct_id)) + displaymarking=obj.displaymarking, msg_stanza_id=obj.id_, + correct_id=obj.correct_id) else: # otherwise pass it off to the control to be queued self.on_private_message(nick, obj.msgtxt, obj.timestamp, diff --git a/src/roster_window.py b/src/roster_window.py index aa8cbfe41..15f90c7df 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -1859,7 +1859,7 @@ class RosterWindow: # with them session = gajim.connections[account].make_new_session(jid) - tim = time.localtime(float(result[2])) + tim = float(result[2]) session.roster_message(jid, result[1], tim, msg_type='chat', msg_log_id=result[0], additional_data=additional_data) gajim.logger.set_shown_unread_msgs(result[0]) @@ -2745,7 +2745,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_log_id=obj.msg_log_id, correct_id=(obj.id_, obj.correct_id), + msg_log_id=obj.msg_log_id, msg_stanza_id=obj.id_, correct_id=obj.correct_id, xep0184_id=xep0184_id) if obj.msg_log_id: pw = obj.session.control.parent_win