diff --git a/src/chat_control.py b/src/chat_control.py index 61ae054d1..bf1680469 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -341,7 +341,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 @@ -785,7 +784,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) @@ -841,7 +839,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) @@ -852,26 +850,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) @@ -1364,6 +1348,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') @@ -2282,7 +2267,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'): @@ -2293,22 +2278,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): @@ -2422,7 +2406,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 @@ -2487,7 +2472,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: @@ -2961,7 +2947,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 378f8db30..f749db5b8 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 0a8a022ce..900cd807c 100644 --- a/src/conversation_textview.py +++ b/src/conversation_textview.py @@ -57,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'): @@ -154,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) @@ -185,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) @@ -206,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 @@ -225,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 @@ -326,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')) @@ -398,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) @@ -448,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: @@ -639,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 @@ -708,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: @@ -716,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) @@ -1244,62 +1229,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: @@ -1307,90 +1335,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=''): """ @@ -1429,6 +1463,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' @@ -1448,12 +1510,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) @@ -1474,10 +1539,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')): @@ -1492,16 +1562,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 b4427a4ef..f54b95d99 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -1858,7 +1858,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]) @@ -2744,7 +2744,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