Philipp Hörist pushed to branch master at gajim / gajim

Commits:

7 changed files:

Changes:

  • src/chat_control.py
    ... ... @@ -341,7 +341,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
    341 341
             self.was_at_the_end = True
    
    342 342
             self.correcting = False
    
    343 343
             self.last_sent_msg = None
    
    344
    -        self.last_sent_txt = None
    
    345 344
             self.last_received_txt = {} # one per name
    
    346 345
             self.last_received_id = {} # one per name
    
    347 346
     
    
    ... ... @@ -785,7 +784,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
    785 784
     
    
    786 785
             def _cb(obj, msg, cb, *cb_args):
    
    787 786
                 self.last_sent_msg = msg
    
    788
    -            self.last_sent_txt = cb_args[0]
    
    789 787
                 if cb:
    
    790 788
                     cb(obj, msg, *cb_args)
    
    791 789
     
    
    ... ... @@ -841,7 +839,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
    841 839
         other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[],
    
    842 840
         count_as_new=True, subject=None, old_kind=None, xhtml=None, simple=False,
    
    843 841
         xep0184_id=None, graphics=True, displaymarking=None, msg_log_id=None,
    
    844
    -    correct_id=None, additional_data={}):
    
    842
    +    msg_stanza_id=None, correct_id=None, additional_data={}):
    
    845 843
             """
    
    846 844
             Print 'chat' type messages
    
    847 845
             correct_id = (message_id, correct_id)
    
    ... ... @@ -852,26 +850,12 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
    852 850
             end = False
    
    853 851
             if self.was_at_the_end or kind == 'outgoing':
    
    854 852
                 end = True
    
    855
    -        old_txt = ''
    
    856
    -        if name in self.last_received_txt:
    
    857
    -            old_txt = self.last_received_txt[name]
    
    858
    -        if correct_id and correct_id[1] and \
    
    859
    -        name in self.conv_textview.last_received_message_marks and \
    
    860
    -        correct_id[1] == self.last_received_id[name]:
    
    861
    -            self.conv_textview.correct_last_received_message(text, xhtml,
    
    862
    -                name, old_txt)
    
    863
    -        elif correct_id and correct_id[1] and \
    
    864
    -        self.conv_textview.last_sent_message_marks[0] and \
    
    865
    -        correct_id[1] == self.last_received_id[name]:
    
    866
    -            # this is for carbon copied messages that are sent from another
    
    867
    -            # resource
    
    868
    -            self.conv_textview.correct_last_sent_message(text, xhtml,
    
    869
    -                self.get_our_nick(), old_txt, additional_data=additional_data)
    
    870
    -        else:
    
    871
    -            textview.print_conversation_line(text, jid, kind, name, tim,
    
    872
    -                other_tags_for_name, other_tags_for_time, other_tags_for_text,
    
    873
    -                subject, old_kind, xhtml, simple=simple, graphics=graphics,
    
    874
    -                displaymarking=displaymarking, additional_data=additional_data)
    
    853
    +
    
    854
    +        textview.print_conversation_line(text, jid, kind, name, tim,
    
    855
    +            other_tags_for_name, other_tags_for_time, other_tags_for_text,
    
    856
    +            subject, old_kind, xhtml, simple=simple, graphics=graphics,
    
    857
    +            displaymarking=displaymarking, msg_stanza_id=msg_stanza_id,
    
    858
    +            correct_id=correct_id, additional_data=additional_data)
    
    875 859
     
    
    876 860
             if xep0184_id is not None:
    
    877 861
                 textview.show_xep0184_warning(xep0184_id)
    
    ... ... @@ -1364,6 +1348,7 @@ class ChatControl(ChatControlBase):
    1364 1348
             self.gpg_is_active = False
    
    1365 1349
             self.last_recv_message_id = None
    
    1366 1350
             self.last_recv_message_marks = None
    
    1351
    +        self.last_message_timestamp = None
    
    1367 1352
             # for muc use:
    
    1368 1353
             # widget = self.xml.get_object('muc_window_actions_button')
    
    1369 1354
             self.actions_button = self.xml.get_object('message_window_actions_button')
    
    ... ... @@ -2282,7 +2267,7 @@ class ChatControl(ChatControlBase):
    2282 2267
                     GLib.source_remove(self.possible_inactive_timeout_id)
    
    2283 2268
                     self._schedule_activity_timers()
    
    2284 2269
     
    
    2285
    -        def _on_sent(obj, msg_stanza, message, encrypted, xhtml, label, old_txt):
    
    2270
    +        def _on_sent(obj, msg_stanza, message, encrypted, xhtml, label):
    
    2286 2271
                 id_ = msg_stanza.getID()
    
    2287 2272
                 if self.contact.supports(NS_RECEIPTS) and gajim.config.get_per(
    
    2288 2273
                 'accounts', self.account, 'request_receipt'):
    
    ... ... @@ -2293,22 +2278,21 @@ class ChatControl(ChatControlBase):
    2293 2278
                     displaymarking = label.getTag('displaymarking')
    
    2294 2279
                 else:
    
    2295 2280
                     displaymarking = None
    
    2296
    -            if self.correcting and \
    
    2297
    -            self.conv_textview.last_sent_message_marks[0]:
    
    2298
    -                self.conv_textview.correct_last_sent_message(message, xhtml,
    
    2299
    -                    self.get_our_nick(), old_txt, additional_data=obj.additional_data)
    
    2281
    +            if self.correcting:
    
    2300 2282
                     self.correcting = False
    
    2301 2283
                     self.msg_textview.override_background_color(
    
    2302 2284
                         Gtk.StateType.NORMAL, self.old_message_tv_color)
    
    2303
    -                return
    
    2285
    +
    
    2304 2286
                 self.print_conversation(message, self.contact.jid,
    
    2305 2287
                     encrypted=encrypted, xep0184_id=xep0184_id, xhtml=xhtml,
    
    2306
    -                displaymarking=displaymarking, additional_data=obj.additional_data)
    
    2288
    +                displaymarking=displaymarking, msg_stanza_id=id_,
    
    2289
    +                correct_id=msg_stanza.getTagAttr('replace', 'id'),
    
    2290
    +                additional_data=obj.additional_data)
    
    2307 2291
     
    
    2308 2292
             ChatControlBase.send_message(self, message, keyID, type_='chat',
    
    2309 2293
                 chatstate=chatstate_to_send, xhtml=xhtml, callback=_on_sent,
    
    2310
    -            callback_args=[message, encrypted, xhtml, self.get_seclabel(),
    
    2311
    -            self.last_sent_txt], process_commands=process_commands,
    
    2294
    +            callback_args=[message, encrypted, xhtml, self.get_seclabel()],
    
    2295
    +            process_commands=process_commands,
    
    2312 2296
                 attention=attention)
    
    2313 2297
     
    
    2314 2298
         def check_for_possible_paused_chatstate(self, arg):
    
    ... ... @@ -2422,7 +2406,8 @@ class ChatControl(ChatControlBase):
    2422 2406
     
    
    2423 2407
         def print_conversation(self, text, frm='', tim=None, encrypted=False,
    
    2424 2408
         subject=None, xhtml=None, simple=False, xep0184_id=None,
    
    2425
    -    displaymarking=None, msg_log_id=None, correct_id=None, additional_data={}):
    
    2409
    +    displaymarking=None, msg_log_id=None, correct_id=None,
    
    2410
    +    msg_stanza_id=None, additional_data={}):
    
    2426 2411
             """
    
    2427 2412
             Print a line in the conversation
    
    2428 2413
     
    
    ... ... @@ -2487,7 +2472,8 @@ class ChatControl(ChatControlBase):
    2487 2472
             ChatControlBase.print_conversation_line(self, text, kind, name, tim,
    
    2488 2473
                 subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml,
    
    2489 2474
                 simple=simple, xep0184_id=xep0184_id, displaymarking=displaymarking,
    
    2490
    -            msg_log_id=msg_log_id, correct_id=correct_id, additional_data=additional_data)
    
    2475
    +            msg_log_id=msg_log_id, msg_stanza_id=msg_stanza_id,
    
    2476
    +            correct_id=correct_id, additional_data=additional_data)
    
    2491 2477
             if text.startswith('/me ') or text.startswith('/me\n'):
    
    2492 2478
                 self.old_msg_kind = None
    
    2493 2479
             else:
    
    ... ... @@ -2961,7 +2947,7 @@ class ChatControl(ChatControlBase):
    2961 2947
                     kind = 'status'
    
    2962 2948
                     name = self.contact.get_shown_name()
    
    2963 2949
     
    
    2964
    -            tim = time.localtime(float(row[0]))
    
    2950
    +            tim = float(row[0])
    
    2965 2951
     
    
    2966 2952
                 if gajim.config.get('restored_messages_small'):
    
    2967 2953
                     small_attr = ['small']
    

  • src/common/connection_handlers.py
    ... ... @@ -1032,7 +1032,7 @@ class ConnectionHandlersBase:
    1032 1032
                 # Do not override assigned key
    
    1033 1033
                 obj.contact.keyID = obj.keyID
    
    1034 1034
             if obj.timestamp:
    
    1035
    -            obj.contact.last_status_time = obj.timestamp
    
    1035
    +            obj.contact.last_status_time = localtime(obj.timestamp)
    
    1036 1036
             elif not gajim.block_signed_in_notifications[account]:
    
    1037 1037
                 # We're connected since more that 30 seconds
    
    1038 1038
                 obj.contact.last_status_time = localtime()
    

  • src/common/connection_handlers_events.py
    ... ... @@ -94,12 +94,16 @@ class HelperEvent:
    94 94
                 self.gc_control = minimized.get(self.jid)
    
    95 95
     
    
    96 96
         def _generate_timestamp(self, tag):
    
    97
    +        # Make sure we use only int/float Epoch time
    
    98
    +        if not isinstance(tag, str):
    
    99
    +            self.timestamp = time_time()
    
    100
    +            return
    
    97 101
             try:
    
    98 102
                 tim = helpers.datetime_tuple(tag)
    
    103
    +            self.timestamp = timegm(tim)
    
    99 104
             except Exception:
    
    100 105
                 log.error('wrong timestamp, ignoring it: ' + tag)
    
    101
    -            tim = localtime()
    
    102
    -        self.timestamp = localtime(timegm(tim))
    
    106
    +            self.timestamp = time_time()
    
    103 107
     
    
    104 108
         def get_chatstate(self):
    
    105 109
             """
    
    ... ... @@ -1059,7 +1063,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1059 1063
                 return
    
    1060 1064
             tim = delay.getAttr('stamp')
    
    1061 1065
             tim = helpers.datetime_tuple(tim)
    
    1062
    -        self.tim = localtime(timegm(tim))
    
    1066
    +        self.tim = timegm(tim)
    
    1063 1067
             to_ = self.msg_.getAttr('to')
    
    1064 1068
             if to_:
    
    1065 1069
                 to_ = gajim.get_jid_without_resource(to_)
    
    ... ... @@ -1278,7 +1282,7 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1278 1282
     
    
    1279 1283
                     if not form.getField('FORM_TYPE'):
    
    1280 1284
                         return
    
    1281
    -                
    
    1285
    +
    
    1282 1286
                     if form['FORM_TYPE'] == 'urn:xmpp:ssn':
    
    1283 1287
                         self.session.handle_negotiation(form)
    
    1284 1288
                     else:
    
    ... ... @@ -1302,6 +1306,7 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1302 1306
     
    
    1303 1307
             self._generate_timestamp(self.stanza.getTimestamp())
    
    1304 1308
     
    
    1309
    +
    
    1305 1310
             self.encrypted = False
    
    1306 1311
             xep_200_encrypted = self.stanza.getTag('c',
    
    1307 1312
                 namespace=nbxmpp.NS_STANZA_CRYPTO)
    
    ... ... @@ -1522,6 +1527,7 @@ class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
    1522 1527
             self.stanza = self.msg_obj.stanza
    
    1523 1528
             if not hasattr(self, 'additional_data'):
    
    1524 1529
                 self.additional_data = self.msg_obj.additional_data
    
    1530
    +        self.id_ = self.msg_obj.stanza.getID()
    
    1525 1531
             self.fjid = self.msg_obj.fjid
    
    1526 1532
             self.msgtxt = self.msg_obj.msgtxt
    
    1527 1533
             self.jid = self.msg_obj.jid
    

  • src/common/logger.py
    ... ... @@ -523,9 +523,9 @@ class Logger:
    523 523
             subject_col = subject
    
    524 524
             additional_data_col = json.dumps(additional_data)
    
    525 525
             if tim:
    
    526
    -            time_col = int(float(time.mktime(tim)))
    
    526
    +            time_col = float(tim)
    
    527 527
             else:
    
    528
    -            time_col = int(float(time.time()))
    
    528
    +            time_col = float(time.time())
    
    529 529
     
    
    530 530
             kind_col, show_col = self.convert_human_values_to_db_api_values(kind,
    
    531 531
                     show)
    
    ... ... @@ -1112,9 +1112,9 @@ class Logger:
    1112 1112
     
    
    1113 1113
         def save_if_not_exists(self, with_, direction, tim, msg='', nick=None, additional_data={}):
    
    1114 1114
             if tim:
    
    1115
    -            time_col = int(float(time.mktime(tim)))
    
    1115
    +            time_col = float(tim)
    
    1116 1116
             else:
    
    1117
    -            time_col = int(float(time.time()))
    
    1117
    +            time_col = float(time.time())
    
    1118 1118
             if not msg:
    
    1119 1119
                 return
    
    1120 1120
             if self.jid_is_from_pm(with_) or nick:
    
    ... ... @@ -1157,7 +1157,7 @@ class Logger:
    1157 1157
             self.write(type_, with_, message=msg, tim=tim, additional_data=additional_data)
    
    1158 1158
     
    
    1159 1159
         def _nec_gc_message_received(self, obj):
    
    1160
    -        tim_f = float(time.mktime(obj.timestamp))
    
    1160
    +        tim_f = float(obj.timestamp)
    
    1161 1161
             tim_int = int(tim_f)
    
    1162 1162
             if gajim.config.should_log(obj.conn.name, obj.jid) and not \
    
    1163 1163
             tim_int < obj.conn.last_history_time[obj.jid] and obj.msgtxt and \
    

  • src/conversation_textview.py
    ... ... @@ -57,6 +57,9 @@ NOT_SHOWN = 0
    57 57
     ALREADY_RECEIVED = 1
    
    58 58
     SHOWN = 2
    
    59 59
     
    
    60
    +import logging
    
    61
    +log = logging.getLogger('gajim.conversation_textview')
    
    62
    +
    
    60 63
     def is_selection_modified(mark):
    
    61 64
         name = mark.get_name()
    
    62 65
         if name and name in ('selection_bound', 'insert'):
    
    ... ... @@ -154,7 +157,6 @@ class TextViewImage(Gtk.Image):
    154 157
             if is_selection_modified(mark):
    
    155 158
                 self._update_selected()
    
    156 159
     
    
    157
    -
    
    158 160
     class ConversationTextview(GObject.GObject):
    
    159 161
         """
    
    160 162
         Class for the conversation textview (where user reads already said messages)
    
    ... ... @@ -185,14 +187,17 @@ class ConversationTextview(GObject.GObject):
    185 187
             """
    
    186 188
             GObject.GObject.__init__(self)
    
    187 189
             self.used_in_history_window = used_in_history_window
    
    188
    -
    
    190
    +        self.line = 0
    
    191
    +        self.message_list = []
    
    192
    +        self.corrected_text_list = {}
    
    189 193
             self.fc = FuzzyClock()
    
    190 194
     
    
    191
    -
    
    192 195
             # no need to inherit TextView, use it as atrribute is safer
    
    193 196
             self.tv = HtmlTextView()
    
    194 197
             self.tv.hyperlink_handler = self.hyperlink_handler
    
    195 198
     
    
    199
    +        self.tv.set_has_tooltip(True)
    
    200
    +
    
    196 201
             # set properties
    
    197 202
             self.tv.set_border_width(1)
    
    198 203
             self.tv.set_accepts_tab(True)
    
    ... ... @@ -206,9 +211,10 @@ class ConversationTextview(GObject.GObject):
    206 211
             self.image_cache = {}
    
    207 212
             self.xep0184_marks = {}
    
    208 213
             self.xep0184_shown = {}
    
    209
    -        self.last_sent_message_marks = [None, None]
    
    210
    -        # A pair per occupant. Key is '' in normal chat
    
    211
    -        self.last_received_message_marks = {}
    
    214
    +        # self.last_sent_message_id = msg_stanza_id
    
    215
    +        self.last_sent_message_id = None
    
    216
    +        # last_received_message_id[name] = (msg_stanza_id, line_start_mark)
    
    217
    +        self.last_received_message_id = {}
    
    212 218
     
    
    213 219
             # It's True when we scroll in the code, so we can detect scroll from user
    
    214 220
             self.auto_scrolling = False
    
    ... ... @@ -225,6 +231,8 @@ class ConversationTextview(GObject.GObject):
    225 231
     
    
    226 232
             id_ = self.tv.connect('draw', self.on_textview_draw)
    
    227 233
             self.handlers[id_] = self.tv
    
    234
    +        id_ = self.tv.connect('query-tooltip', self.query_tooltip)
    
    235
    +        self.handlers[id_] = self.tv
    
    228 236
     
    
    229 237
     
    
    230 238
             self.account = account
    
    ... ... @@ -326,18 +334,39 @@ class ConversationTextview(GObject.GObject):
    326 334
     
    
    327 335
             self.xep0184_warning_tooltip = tooltips.BaseTooltip()
    
    328 336
     
    
    329
    -        self.line_tooltip = tooltips.BaseTooltip()
    
    330 337
             self.smooth_id = None
    
    331 338
             self.just_cleared = False
    
    332 339
     
    
    340
    +    def query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
    
    341
    +        x_pos, y_pos = self.tv.window_to_buffer_coords(
    
    342
    +            Gtk.TextWindowType.TEXT, x_pos, y_pos)
    
    343
    +        if Gtk.MINOR_VERSION > 18:
    
    344
    +            iter_ = self.tv.get_iter_at_position(x_pos, y_pos)[1]
    
    345
    +        else:
    
    346
    +            iter_ = self.tv.get_iter_at_position(x_pos, y_pos)[0]
    
    347
    +        for tag in iter_.get_tags():
    
    348
    +            tag_name = tag.get_property('name')
    
    349
    +            if tag_name == 'focus-out-line':
    
    350
    +                tooltip.set_text(_(
    
    351
    +                    'Text below this line is what has '
    
    352
    +                    'been said since the\nlast time you paid attention to this '
    
    353
    +                    'group chat'))
    
    354
    +                return True
    
    355
    +
    
    356
    +            try:
    
    357
    +                text = self.corrected_text_list[tag_name]
    
    358
    +                tooltip.set_markup(text)
    
    359
    +                return True
    
    360
    +            except KeyError:
    
    361
    +                pass
    
    362
    +        return False
    
    363
    +
    
    333 364
         def del_handlers(self):
    
    334 365
             for i in self.handlers.keys():
    
    335 366
                 if self.handlers[i].handler_is_connected(i):
    
    336 367
                     self.handlers[i].disconnect(i)
    
    337 368
             del self.handlers
    
    338 369
             self.tv.destroy()
    
    339
    -        #FIXME:
    
    340
    -        # self.line_tooltip.destroy()
    
    341 370
     
    
    342 371
         def update_tags(self):
    
    343 372
             self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor'))
    
    ... ... @@ -398,7 +427,7 @@ class ConversationTextview(GObject.GObject):
    398 427
                 self.auto_scrolling = False
    
    399 428
     
    
    400 429
         def smooth_scroll_to_end(self):
    
    401
    -        if None != self.smooth_id: # already scrolling
    
    430
    +        if self.smooth_id is not None:  # already scrolling
    
    402 431
                 return False
    
    403 432
             self.smooth_id = GLib.timeout_add(self.SCROLL_DELAY,
    
    404 433
                     self.smooth_scroll)
    
    ... ... @@ -448,55 +477,46 @@ class ConversationTextview(GObject.GObject):
    448 477
                 self.smooth_id = None
    
    449 478
                 self.smooth_scroll_timer.cancel()
    
    450 479
     
    
    451
    -    def show_corrected_message_warning(self, iter_, text=''):
    
    452
    -        buffer_ = self.tv.get_buffer()
    
    453
    -        buffer_.begin_user_action()
    
    454
    -        buffer_.insert(iter_, ' ')
    
    455
    -        anchor = buffer_.create_child_anchor(iter_)
    
    456
    -        img = TextViewImage(anchor, text)
    
    457
    -        img.set_from_pixbuf(ConversationTextview.MESSAGE_CORRECTED_PIXBUF)
    
    458
    -        img.show()
    
    459
    -        self.tv.add_child_at_anchor(img, anchor)
    
    460
    -        buffer_.end_user_action()
    
    480
    +    def correct_message(self, correct_id, kind, name):
    
    481
    +        allowed = True
    
    482
    +        if kind == 'incoming':
    
    483
    +            try:
    
    484
    +                if correct_id in self.last_received_message_id[name]:
    
    485
    +                    start_mark = self.last_received_message_id[name][1]
    
    486
    +                else:
    
    487
    +                    allowed = False
    
    488
    +            except KeyError:
    
    489
    +                allowed = False
    
    490
    +        elif kind == 'outgoing':
    
    491
    +            if self.last_sent_message_id[0] == correct_id:
    
    492
    +                start_mark = self.last_sent_message_id[1]
    
    493
    +            else:
    
    494
    +                allowed = False
    
    495
    +        else:
    
    496
    +            allowed = False
    
    497
    +
    
    498
    +        if not allowed:
    
    499
    +            log.debug('Message correctiong not allowed')
    
    500
    +            return None
    
    501
    +
    
    502
    +        end_mark, index = self.get_end_mark(correct_id, start_mark)
    
    503
    +        if not index:
    
    504
    +            log.debug('Could not find line to correct')
    
    505
    +            return None
    
    461 506
     
    
    462
    -    def correct_last_sent_message(self, message, xhtml, name, old_txt, additional_data={}):
    
    463
    -        m1 = self.last_sent_message_marks[0]
    
    464
    -        m2 = self.last_sent_message_marks[1]
    
    465
    -        buffer_ = self.tv.get_buffer()
    
    466
    -        i1 = buffer_.get_iter_at_mark(m1)
    
    467
    -        i2 = buffer_.get_iter_at_mark(m2)
    
    468
    -        txt = buffer_.get_text(i1, i2, True)
    
    469
    -        buffer_.delete(i1, i2)
    
    470
    -        tag = 'outgoingtxt'
    
    471
    -        if message.startswith('/me'):
    
    472
    -            tag = 'outgoing'
    
    473
    -        i2 = self.print_conversation_line(message, '', 'outgoing', name, None,
    
    474
    -            xhtml=xhtml, iter_=i1, additional_data=additional_data)
    
    475
    -        tt_txt = _('<b>Message was corrected. Last message was:</b>\n  %s') % \
    
    476
    -            GLib.markup_escape_text(old_txt)
    
    477
    -        self.show_corrected_message_warning(i2, tt_txt)
    
    478
    -        self.last_sent_message_marks[1] = buffer_.create_mark(None, i2,
    
    479
    -            left_gravity=True)
    
    480
    -
    
    481
    -    def correct_last_received_message(self, message, xhtml, name, old_txt,
    
    482
    -    other_tags_for_name=[], other_tags_for_text=[], additional_data={}):
    
    483
    -        if name not in self.last_received_message_marks:
    
    484
    -            return
    
    485
    -        m1 = self.last_received_message_marks[name][0]
    
    486
    -        m2 = self.last_received_message_marks[name][1]
    
    487 507
             buffer_ = self.tv.get_buffer()
    
    488
    -        i1 = buffer_.get_iter_at_mark(m1)
    
    489
    -        i2 = buffer_.get_iter_at_mark(m2)
    
    490
    -        txt = buffer_.get_text(i1, i2, True)
    
    491
    -        buffer_.delete(i1, i2)
    
    492
    -        i2 = self.print_conversation_line(message, '', 'incoming', name, None,
    
    493
    -            other_tags_for_name=other_tags_for_name,
    
    494
    -            other_tags_for_text=other_tags_for_text, xhtml=xhtml, iter_=i1, additional_data=additional_data)
    
    495
    -        tt_txt = _('<b>Message was corrected. Last message was:</b>\n  %s') % \
    
    496
    -            GLib.markup_escape_text(old_txt)
    
    497
    -        self.show_corrected_message_warning(i2, tt_txt)
    
    498
    -        self.last_received_message_marks[name][1] = buffer_.create_mark(None, i2,
    
    499
    -            left_gravity=True)
    
    508
    +        if not end_mark:
    
    509
    +            end_iter = self.tv.get_buffer().get_end_iter()
    
    510
    +        else:
    
    511
    +            end_iter = buffer_.get_iter_at_mark(end_mark)
    
    512
    +
    
    513
    +        start_iter = buffer_.get_iter_at_mark(start_mark)
    
    514
    +
    
    515
    +        old_txt = buffer_.get_text(start_iter, end_iter, True)
    
    516
    +        buffer_.delete(start_iter, end_iter)
    
    517
    +        buffer_.delete_mark(start_mark)
    
    518
    +
    
    519
    +        return index, end_mark, old_txt
    
    500 520
     
    
    501 521
         def show_xep0184_warning(self, id_):
    
    502 522
             if id_ in self.xep0184_marks:
    
    ... ... @@ -639,30 +659,6 @@ class ConversationTextview(GObject.GObject):
    639 659
                     "end. If this icon stays\nfor a long time, it's likely the "
    
    640 660
                     'message got lost.'), 8, position[1] + y)
    
    641 661
     
    
    642
    -    def show_line_tooltip(self):
    
    643
    -        self.line_tooltip.timeout = 0
    
    644
    -        w = self.tv.get_window(Gtk.TextWindowType.TEXT)
    
    645
    -        device = w.get_display().get_device_manager().get_client_pointer()
    
    646
    -        pointer = w.get_device_position(device)
    
    647
    -        x = pointer[1]
    
    648
    -        y = pointer[2]
    
    649
    -        iter_ = self.tv.get_iter_at_location(x, y)
    
    650
    -        if isinstance(iter_, tuple):
    
    651
    -            iter_ = iter_[1]
    
    652
    -        tags = iter_.get_tags()
    
    653
    -        tag_table = self.tv.get_buffer().get_tag_table()
    
    654
    -        over_line = False
    
    655
    -        for tag in tags:
    
    656
    -            if tag == tag_table.lookup('focus-out-line'):
    
    657
    -                over_line = True
    
    658
    -                break
    
    659
    -        if over_line and not self.line_tooltip.win:
    
    660
    -            # check if the current pointer is still over the line
    
    661
    -            position = w.get_origin()[1:]
    
    662
    -            self.line_tooltip.show_tooltip(_('Text below this line is what has '
    
    663
    -                'been said since the\nlast time you paid attention to this '
    
    664
    -                'group chat'), 8, position[1] + y)
    
    665
    -
    
    666 662
         def on_textview_draw(self, widget, ctx):
    
    667 663
             return
    
    668 664
             #TODO
    
    ... ... @@ -708,7 +704,6 @@ class ConversationTextview(GObject.GObject):
    708 704
                 w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.XTERM))
    
    709 705
                 self.change_cursor = False
    
    710 706
             tag_table = self.tv.get_buffer().get_tag_table()
    
    711
    -        over_line = False
    
    712 707
             xep0184_warning = False
    
    713 708
     
    
    714 709
             for tag in tags:
    
    ... ... @@ -716,25 +711,15 @@ class ConversationTextview(GObject.GObject):
    716 711
                 tag_table.lookup('xmpp'), tag_table.lookup('sth_at_sth')):
    
    717 712
                     w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND2))
    
    718 713
                     self.change_cursor = True
    
    719
    -            elif tag == tag_table.lookup('focus-out-line'):
    
    720
    -                over_line = True
    
    721 714
                 elif tag == tag_table.lookup('xep0184-warning'):
    
    722 715
                     xep0184_warning = True
    
    723 716
     
    
    724
    -        if self.line_tooltip.timeout != 0 or self.line_tooltip.shown:
    
    725
    -            # Check if we should hide the line tooltip
    
    726
    -            if not over_line:
    
    727
    -                self.line_tooltip.hide_tooltip()
    
    728 717
             if self.xep0184_warning_tooltip.timeout != 0 or \
    
    729 718
             self.xep0184_warning_tooltip.shown:
    
    730 719
                 # Check if we should hide the XEP-184 warning tooltip
    
    731 720
                 if not xep0184_warning:
    
    732 721
                     self.xep0184_warning_tooltip.hide_tooltip()
    
    733
    -        if over_line and not self.line_tooltip.win:
    
    734
    -            self.line_tooltip.timeout = GLib.timeout_add(500,
    
    735
    -                    self.show_line_tooltip)
    
    736
    -            w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR))
    
    737
    -            self.change_cursor = True
    
    722
    +
    
    738 723
             if xep0184_warning and not self.xep0184_warning_tooltip.win:
    
    739 724
                 self.xep0184_warning_tooltip.timeout = GLib.timeout_add(500,
    
    740 725
                         self.show_xep0184_warning_tooltip)
    
    ... ... @@ -1244,62 +1229,105 @@ class ConversationTextview(GObject.GObject):
    1244 1229
             buffer_.insert_with_tags_by_name(iter_, '\n', 'eol')
    
    1245 1230
             self.just_cleared = False
    
    1246 1231
     
    
    1232
    +    def get_end_mark(self, msg_stanza_id, start_mark):
    
    1233
    +        for index, msg in enumerate(self.message_list):
    
    1234
    +            if msg[2] == msg_stanza_id and msg[1] == start_mark:
    
    1235
    +                try:
    
    1236
    +                    end_mark = self.message_list[index + 1][1]
    
    1237
    +                    end_mark_name = end_mark.get_name()
    
    1238
    +                except IndexError:
    
    1239
    +                    # We are at the last message
    
    1240
    +                    end_mark = None
    
    1241
    +                    end_mark_name = None
    
    1242
    +
    
    1243
    +                log.debug('start mark: %s, end mark: %s, '
    
    1244
    +                          'replace message-list index: %s',
    
    1245
    +                          start_mark.get_name(), end_mark_name, index)
    
    1246
    +
    
    1247
    +                return end_mark, index
    
    1248
    +        log.debug('stanza-id not in message list')
    
    1249
    +        return None, None
    
    1250
    +
    
    1251
    +    def get_insert_mark(self, timestamp):
    
    1252
    +        # message_list = [(timestamp, line_start_mark, msg_stanza_id)]
    
    1253
    +        # We check if this is a new Message
    
    1254
    +        try:
    
    1255
    +            if self.message_list[-1][0] <= timestamp:
    
    1256
    +                return None, None
    
    1257
    +        except IndexError:
    
    1258
    +            # We have no Messages in the TextView
    
    1259
    +            return None, None
    
    1260
    +
    
    1261
    +        # Not a new Message
    
    1262
    +        # Search for insertion point
    
    1263
    +        for index, msg in enumerate(self.message_list):
    
    1264
    +            if msg[0] > timestamp:
    
    1265
    +                return msg[1], index
    
    1266
    +
    
    1267
    +        # Should not happen, but who knows
    
    1268
    +        return None, None
    
    1269
    +
    
    1247 1270
         def print_conversation_line(self, text, jid, kind, name, tim,
    
    1248
    -    other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[],
    
    1271
    +    other_tags_for_name=None, other_tags_for_time=None, other_tags_for_text=None,
    
    1249 1272
         subject=None, old_kind=None, xhtml=None, simple=False, graphics=True,
    
    1250
    -    displaymarking=None, iter_=None, additional_data={}):
    
    1273
    +    displaymarking=None, msg_stanza_id=None, correct_id=None, additional_data=None):
    
    1251 1274
             """
    
    1252 1275
             Print 'chat' type messages
    
    1253 1276
             """
    
    1254 1277
             buffer_ = self.tv.get_buffer()
    
    1255 1278
             buffer_.begin_user_action()
    
    1256
    -        if iter_:
    
    1257
    -            temp_mark = buffer_.create_mark(None, iter_, left_gravity=True)
    
    1258
    -        if self.marks_queue.full():
    
    1259
    -            # remove oldest line
    
    1260
    -            m1 = self.marks_queue.get()
    
    1261
    -            m2 = self.marks_queue.get()
    
    1262
    -            i1 = buffer_.get_iter_at_mark(m1)
    
    1263
    -            i2 = buffer_.get_iter_at_mark(m2)
    
    1264
    -            buffer_.delete(i1, i2)
    
    1265
    -            buffer_.delete_mark(m1)
    
    1266
    -        if iter_:
    
    1267
    -            end_iter = buffer_.get_iter_at_mark(temp_mark)
    
    1268
    -            buffer_.delete_mark(temp_mark)
    
    1269
    -        else:
    
    1270
    -            end_iter = buffer_.get_end_iter()
    
    1271
    -        end_offset = end_iter.get_offset()
    
    1272
    -        at_the_end = self.at_the_end()
    
    1273
    -        move_selection = False
    
    1274
    -        if buffer_.get_has_selection() and buffer_.get_selection_bounds()[1].\
    
    1275
    -        get_offset() == end_offset:
    
    1276
    -            move_selection = True
    
    1279
    +        insert_mark = None
    
    1280
    +        insert_mark_name = None
    
    1277 1281
     
    
    1278
    -        if not iter_:
    
    1279
    -            # Create one mark and add it to queue once if it's the first line
    
    1280
    -            # else twice (one for end bound, one for start bound)
    
    1281
    -            mark = None
    
    1282
    -            if buffer_.get_char_count() > 0:
    
    1283
    -                if not simple and not iter_:
    
    1284
    -                    buffer_.insert_with_tags_by_name(end_iter, '\n', 'eol')
    
    1285
    -                    if move_selection:
    
    1286
    -                        sel_start, sel_end = buffer_.get_selection_bounds()
    
    1287
    -                        sel_end.backward_char()
    
    1288
    -                        buffer_.select_range(sel_start, sel_end)
    
    1289
    -                mark = buffer_.create_mark(None, end_iter, left_gravity=True)
    
    1290
    -                self.marks_queue.put(mark)
    
    1291
    -            if not mark:
    
    1292
    -                mark = buffer_.create_mark(None, end_iter, left_gravity=True)
    
    1293
    -            self.marks_queue.put(mark)
    
    1294 1282
             if kind == 'incoming_queue':
    
    1295 1283
                 kind = 'incoming'
    
    1296 1284
             if old_kind == 'incoming_queue':
    
    1297 1285
                 old_kind = 'incoming'
    
    1298
    -        # print the time stamp
    
    1286
    +
    
    1299 1287
             if not tim:
    
    1300
    -            # We don't have tim for outgoing messages...
    
    1301
    -            tim = time.localtime()
    
    1302
    -        current_print_time = gajim.config.get('print_time')
    
    1288
    +            # For outgoing Messages and Status prints
    
    1289
    +            tim = time.time()
    
    1290
    +
    
    1291
    +        corrected = False
    
    1292
    +        if correct_id:
    
    1293
    +            try:
    
    1294
    +                index, insert_mark, old_txt = \
    
    1295
    +                    self.correct_message(correct_id, kind, name)
    
    1296
    +                self.corrected_text_list[msg_stanza_id] = \
    
    1297
    +                    '<b>Message was corrected. Last message was:</b>\n{}' \
    
    1298
    +                    .format(GLib.markup_escape_text(old_txt))
    
    1299
    +                corrected = True
    
    1300
    +            except TypeError:
    
    1301
    +                log.debug('Message was not corrected !')
    
    1302
    +
    
    1303
    +        if not corrected:
    
    1304
    +            # Get insertion point into TextView
    
    1305
    +            insert_mark, index = self.get_insert_mark(tim)
    
    1306
    +
    
    1307
    +        if insert_mark:
    
    1308
    +            insert_mark_name = insert_mark.get_name()
    
    1309
    +
    
    1310
    +        log.debug(
    
    1311
    +            'Printed Line: %s, %s, %s, inserted after: %s'
    
    1312
    +            ', stanza-id: %s, correct-id: %s',
    
    1313
    +            self.line, text, tim, insert_mark_name,
    
    1314
    +            msg_stanza_id, correct_id)
    
    1315
    +
    
    1316
    +        if not insert_mark:  # Texview is empty or Message is new
    
    1317
    +            iter_ = buffer_.get_end_iter()
    
    1318
    +            # Insert new Line if Textview is not empty
    
    1319
    +            if buffer_.get_char_count() > 0 and not corrected:
    
    1320
    +                buffer_.insert_with_tags_by_name(iter_, '\n', 'eol')
    
    1321
    +        else:
    
    1322
    +            iter_ = buffer_.get_iter_at_mark(insert_mark)
    
    1323
    +
    
    1324
    +        # Create a temporary mark at the start of the line
    
    1325
    +        # with gravity=Left, so it will not move
    
    1326
    +        # even if we insert directly at the mark iter
    
    1327
    +        temp_mark = buffer_.create_mark('temp', iter_, left_gravity=True)
    
    1328
    +
    
    1329
    +        at_the_end = self.at_the_end()
    
    1330
    +
    
    1303 1331
             if text.startswith('/me '):
    
    1304 1332
                 direction_mark = i18n.paragraph_direction_mark(str(text[3:]))
    
    1305 1333
             else:
    
    ... ... @@ -1307,90 +1335,96 @@ class ConversationTextview(GObject.GObject):
    1307 1335
             # don't apply direction mark if it's status message
    
    1308 1336
             if kind == 'status':
    
    1309 1337
                 direction_mark = i18n.direction_mark
    
    1310
    -        if current_print_time == 'always' and kind != 'info' and not simple:
    
    1311
    -            timestamp_str = self.get_time_to_show(tim, direction_mark)
    
    1312
    -            timestamp = time.strftime(timestamp_str, tim)
    
    1313
    -            timestamp = direction_mark + timestamp + direction_mark
    
    1314
    -            if other_tags_for_time:
    
    1315
    -                buffer_.insert_with_tags_by_name(end_iter, timestamp,
    
    1316
    -                    *other_tags_for_time)
    
    1317
    -            else:
    
    1318
    -                buffer_.insert (end_iter, timestamp)
    
    1319
    -        elif current_print_time == 'sometimes' and kind != 'info' and not simple:
    
    1320
    -            every_foo_seconds = 60 * gajim.config.get(
    
    1321
    -                'print_ichat_every_foo_minutes')
    
    1322
    -            seconds_passed = time.mktime(tim) - self.last_time_printout
    
    1323
    -            if seconds_passed > every_foo_seconds:
    
    1324
    -                self.last_time_printout = time.mktime(tim)
    
    1325
    -                end_iter = buffer_.get_end_iter()
    
    1326
    -                if gajim.config.get('print_time_fuzzy') > 0:
    
    1327
    -                    tim_format = self.fc.fuzzy_time(gajim.config.get('print_time_fuzzy'), tim)
    
    1328
    -                else:
    
    1329
    -                    tim_format = self.get_time_to_show(tim, direction_mark)
    
    1330
    -                buffer_.insert_with_tags_by_name(end_iter, tim_format + '\n',
    
    1331
    -                    'time_sometimes')
    
    1338
    +
    
    1339
    +        # print the time stamp
    
    1340
    +        self.print_time(text, kind, tim, simple, direction_mark,
    
    1341
    +            other_tags_for_time, iter_)
    
    1342
    +
    
    1332 1343
             # If there's a displaymarking, print it here.
    
    1333 1344
             if displaymarking:
    
    1334
    -            self.print_displaymarking(displaymarking, iter_=end_iter)
    
    1345
    +            self.print_displaymarking(displaymarking, iter_=iter_)
    
    1346
    +
    
    1335 1347
             # kind = info, we print things as if it was a status: same color, ...
    
    1336 1348
             if kind in ('error', 'info'):
    
    1337 1349
                 kind = 'status'
    
    1338 1350
             other_text_tag = self.detect_other_text_tag(text, kind)
    
    1339
    -        text_tags = other_tags_for_text[:] # create a new list
    
    1340
    -        mark1 = None
    
    1351
    +        text_tags = []
    
    1352
    +        if other_tags_for_text:
    
    1353
    +            text_tags = other_tags_for_text[:]  # create a new list
    
    1341 1354
             if other_text_tag:
    
    1342
    -            # note that color of /me may be overwritten in gc_control
    
    1343 1355
                 text_tags.append(other_text_tag)
    
    1344
    -            if text.startswith('/me') and not iter_:
    
    1345
    -                mark1 = mark
    
    1346
    -        else: # not status nor /me
    
    1356
    +
    
    1357
    +        else:  # not status nor /me
    
    1347 1358
                 if gajim.config.get('chat_merge_consecutive_nickname'):
    
    1348 1359
                     if kind != old_kind or self.just_cleared:
    
    1349 1360
                         self.print_name(name, kind, other_tags_for_name,
    
    1350
    -                        direction_mark=direction_mark, iter_=end_iter)
    
    1361
    +                        direction_mark=direction_mark, iter_=iter_)
    
    1351 1362
                     else:
    
    1352 1363
                         self.print_real_text(gajim.config.get(
    
    1353 1364
                             'chat_merge_consecutive_nickname_indent'),
    
    1354
    -                        iter_=end_iter, additional_data=additional_data)
    
    1365
    +                        mark=insert_mark, additional_data=additional_data)
    
    1355 1366
                 else:
    
    1356 1367
                     self.print_name(name, kind, other_tags_for_name,
    
    1357
    -                    direction_mark=direction_mark, iter_=end_iter)
    
    1368
    +                    direction_mark=direction_mark, iter_=iter_)
    
    1358 1369
                 if kind == 'incoming':
    
    1359 1370
                     text_tags.append('incomingtxt')
    
    1360
    -                if not iter_:
    
    1361
    -                    mark1 = mark
    
    1362 1371
                 elif kind == 'outgoing':
    
    1363 1372
                     text_tags.append('outgoingtxt')
    
    1364
    -                if not iter_:
    
    1365
    -                    mark1 = mark
    
    1366
    -        self.print_subject(subject, iter_=end_iter)
    
    1367
    -        self.print_real_text(text, text_tags, name, xhtml, graphics=graphics,
    
    1368
    -            iter_=end_iter, additional_data=additional_data)
    
    1369
    -        if not iter_ and mark1:
    
    1370
    -            mark2 = buffer_.create_mark(None, buffer_.get_end_iter(),
    
    1371
    -                left_gravity=True)
    
    1372
    -            if kind == 'incoming':
    
    1373
    -                if name in self.last_received_message_marks:
    
    1374
    -                    m = self.last_received_message_marks[name][1]
    
    1375
    -                    buffer_.delete_mark(m)
    
    1376
    -                self.last_received_message_marks[name] = [mark1, mark2]
    
    1377
    -            elif kind == 'outgoing':
    
    1378
    -                m = self.last_sent_message_marks[1]
    
    1379
    -                if m:
    
    1380
    -                    buffer_.delete_mark(m)
    
    1381
    -                self.last_sent_message_marks = [mark1, mark2]
    
    1382
    -        # scroll to the end of the textview
    
    1383
    -        if at_the_end or kind == 'outgoing':
    
    1384
    -            # we are at the end or we are sending something
    
    1385
    -            # scroll to the end (via idle in case the scrollbar has appeared)
    
    1386
    -            if gajim.config.get('use_smooth_scrolling'):
    
    1387
    -                GLib.idle_add(self.smooth_scroll_to_end)
    
    1388
    -            else:
    
    1389
    -                GLib.idle_add(self.scroll_to_end)
    
    1373
    +
    
    1374
    +        self.print_subject(subject, iter_=iter_)
    
    1375
    +        
    
    1376
    +        iter_ = self.print_real_text(text, text_tags, name, xhtml, graphics=graphics,
    
    1377
    +            mark=insert_mark, additional_data=additional_data)
    
    1378
    +
    
    1379
    +        if corrected:
    
    1380
    +            # Show Correction Icon
    
    1381
    +            buffer_.create_tag(tag_name=msg_stanza_id)
    
    1382
    +            buffer_.insert(iter_, ' ')
    
    1383
    +            buffer_.insert_pixbuf(
    
    1384
    +                iter_, ConversationTextview.MESSAGE_CORRECTED_PIXBUF)
    
    1385
    +            tag_start_iter = iter_.copy()
    
    1386
    +            tag_start_iter.backward_chars(2)
    
    1387
    +            buffer_.apply_tag_by_name(msg_stanza_id, tag_start_iter, iter_)
    
    1388
    +
    
    1389
    +        # If we inserted a Line we add a new line at the end
    
    1390
    +        if insert_mark:
    
    1391
    +            buffer_.insert_with_tags_by_name(iter_, '\n', 'eol')
    
    1392
    +        # We delete the temp mark and replace it with a mark
    
    1393
    +        # that has gravity=right
    
    1394
    +        temp_iter = buffer_.get_iter_at_mark(temp_mark)
    
    1395
    +        buffer_.delete_mark(temp_mark)
    
    1396
    +        new_mark = buffer_.create_mark(
    
    1397
    +            str(self.line), temp_iter, left_gravity=False)
    
    1398
    +
    
    1399
    +        if not index:
    
    1400
    +            # New Message
    
    1401
    +            self.message_list.append((tim, new_mark, msg_stanza_id))
    
    1402
    +        elif corrected:
    
    1403
    +            # Replace the corrected message
    
    1404
    +            self.message_list[index] = (tim, new_mark, msg_stanza_id)
    
    1405
    +        else:
    
    1406
    +            # We insert the message at index
    
    1407
    +            self.message_list.insert(index, (tim, new_mark, msg_stanza_id))
    
    1408
    +
    
    1409
    +        if kind == 'incoming':
    
    1410
    +            self.last_received_message_id[name] = (msg_stanza_id, new_mark)
    
    1411
    +        elif kind == 'outgoing':
    
    1412
    +            self.last_sent_message_id = (msg_stanza_id, new_mark)
    
    1413
    +
    
    1414
    +        if not insert_mark:
    
    1415
    +            if at_the_end or kind == 'outgoing':
    
    1416
    +                # we are at the end or we are sending something
    
    1417
    +                # scroll to the end (via idle in case the scrollbar has appeared)
    
    1418
    +                if gajim.config.get('use_smooth_scrolling'):
    
    1419
    +                    GLib.idle_add(self.smooth_scroll_to_end)
    
    1420
    +                else:
    
    1421
    +                    GLib.idle_add(self.scroll_to_end)
    
    1390 1422
     
    
    1391 1423
             self.just_cleared = False
    
    1392 1424
             buffer_.end_user_action()
    
    1393
    -        return end_iter
    
    1425
    +
    
    1426
    +        self.line += 1
    
    1427
    +        return iter_
    
    1394 1428
     
    
    1395 1429
         def get_time_to_show(self, tim, direction_mark=''):
    
    1396 1430
             """
    
    ... ... @@ -1429,6 +1463,34 @@ class ConversationTextview(GObject.GObject):
    1429 1463
             elif text.startswith('/me ') or text.startswith('/me\n'):
    
    1430 1464
                 return kind
    
    1431 1465
     
    
    1466
    +    def print_time(self, text, kind, tim, simple, direction_mark, other_tags_for_time, iter_):
    
    1467
    +        local_tim = time.localtime(tim)
    
    1468
    +        buffer_ = self.tv.get_buffer()
    
    1469
    +        current_print_time = gajim.config.get('print_time')
    
    1470
    +
    
    1471
    +        if current_print_time == 'always' and kind != 'info' and not simple:
    
    1472
    +            timestamp_str = self.get_time_to_show(local_tim, direction_mark)
    
    1473
    +            timestamp = time.strftime(timestamp_str, local_tim)
    
    1474
    +            timestamp = direction_mark + timestamp + direction_mark
    
    1475
    +            if other_tags_for_time:
    
    1476
    +                buffer_.insert_with_tags_by_name(iter_, timestamp,
    
    1477
    +                    *other_tags_for_time)
    
    1478
    +            else:
    
    1479
    +                buffer_.insert(iter_, timestamp)
    
    1480
    +        elif current_print_time == 'sometimes' and kind != 'info' and not simple:
    
    1481
    +            every_foo_seconds = 60 * gajim.config.get(
    
    1482
    +                'print_ichat_every_foo_minutes')
    
    1483
    +            seconds_passed = tim - self.last_time_printout
    
    1484
    +            if seconds_passed > every_foo_seconds:
    
    1485
    +                self.last_time_printout = tim
    
    1486
    +                if gajim.config.get('print_time_fuzzy') > 0:
    
    1487
    +                    tim_format = self.fc.fuzzy_time(
    
    1488
    +                        gajim.config.get('print_time_fuzzy'), tim)
    
    1489
    +                else:
    
    1490
    +                    tim_format = self.get_time_to_show(local_tim, direction_mark)
    
    1491
    +                buffer_.insert_with_tags_by_name(iter_, tim_format + '\n',
    
    1492
    +                    'time_sometimes')
    
    1493
    +
    
    1432 1494
         def print_displaymarking(self, displaymarking, iter_=None):
    
    1433 1495
             bgcolor = displaymarking.getAttr('bgcolor') or '#FFF'
    
    1434 1496
             fgcolor = displaymarking.getAttr('fgcolor') or '#000'
    
    ... ... @@ -1448,12 +1510,15 @@ class ConversationTextview(GObject.GObject):
    1448 1510
         def print_name(self, name, kind, other_tags_for_name, direction_mark='',
    
    1449 1511
         iter_=None):
    
    1450 1512
             if name:
    
    1513
    +            name_tags = []
    
    1451 1514
                 buffer_ = self.tv.get_buffer()
    
    1452 1515
                 if iter_:
    
    1453 1516
                     end_iter = iter_
    
    1454 1517
                 else:
    
    1455 1518
                     end_iter = buffer_.get_end_iter()
    
    1456
    -            name_tags = other_tags_for_name[:] # create a new list
    
    1519
    +
    
    1520
    +            if other_tags_for_name:
    
    1521
    +                name_tags = other_tags_for_name[:]  # create a new list
    
    1457 1522
                 name_tags.append(kind)
    
    1458 1523
                 before_str = gajim.config.get('before_nickname')
    
    1459 1524
                 before_str = helpers.from_one_line(before_str)
    
    ... ... @@ -1474,10 +1539,15 @@ class ConversationTextview(GObject.GObject):
    1474 1539
                 self.print_empty_line(end_iter)
    
    1475 1540
     
    
    1476 1541
         def print_real_text(self, text, text_tags=[], name=None, xhtml=None,
    
    1477
    -    graphics=True, iter_=None, additional_data={}):
    
    1542
    +    graphics=True, mark=None, additional_data={}):
    
    1478 1543
             """
    
    1479 1544
             Add normal and special text. call this to add text
    
    1480 1545
             """
    
    1546
    +        buffer_ = self.tv.get_buffer()
    
    1547
    +        if not mark:
    
    1548
    +            iter_ = buffer_.get_end_iter()
    
    1549
    +        else:
    
    1550
    +            iter_ = buffer_.get_iter_at_mark(mark)
    
    1481 1551
             if xhtml:
    
    1482 1552
                 try:
    
    1483 1553
                     if name and (text.startswith('/me ') or text.startswith('/me\n')):
    
    ... ... @@ -1492,16 +1562,23 @@ class ConversationTextview(GObject.GObject):
    1492 1562
             if name and (text.startswith('/me ') or text.startswith('/me\n')):
    
    1493 1563
                 text = '* ' + name + text[3:]
    
    1494 1564
                 text_tags.append('italic')
    
    1495
    -        
    
    1565
    +
    
    1496 1566
             # PluginSystem: adding GUI extension point for ConversationTextview
    
    1497 1567
             self.plugin_modified = False
    
    1498 1568
             gajim.plugin_manager.gui_extension_point('print_real_text', self,
    
    1499 1569
                 text, text_tags, graphics, iter_, additional_data)
    
    1570
    +
    
    1500 1571
             if self.plugin_modified:
    
    1501
    -            return self.tv.get_buffer().get_end_iter()
    
    1502
    -        #needed, if buffer is manipulated by plugins without setting plugin_modified to True
    
    1503
    -        iter_ = self.tv.get_buffer().get_end_iter()
    
    1504
    -        
    
    1572
    +            if not mark:
    
    1573
    +                return buffer_.get_end_iter()
    
    1574
    +            else:
    
    1575
    +                return buffer_.get_iter_at_mark(mark)
    
    1576
    +
    
    1577
    +        if not mark:
    
    1578
    +            iter_ = buffer_.get_end_iter()
    
    1579
    +        else:
    
    1580
    +            iter_ = buffer_.get_iter_at_mark(mark)
    
    1581
    +
    
    1505 1582
             # detect urls formatting and if the user has it on emoticons
    
    1506 1583
             return self.detect_and_print_special_text(text, text_tags, graphics=graphics,
    
    1507 1584
                 iter_=iter_, additional_data=additional_data)

  • src/groupchat_control.py
    ... ... @@ -1069,64 +1069,14 @@ class GroupchatControl(ChatControlBase):
    1069 1069
                     # Like that xhtml messages are grayed too.
    
    1070 1070
                     self.print_old_conversation(obj.msgtxt, contact=obj.nick,
    
    1071 1071
                         tim=obj.timestamp, xhtml=None,
    
    1072
    -                    displaymarking=obj.displaymarking)
    
    1072
    +                    displaymarking=obj.displaymarking, msg_stanza_id=obj.id_)
    
    1073 1073
                 else:
    
    1074
    -                if obj.nick in self.last_received_txt and obj.correct_id and \
    
    1075
    -                obj.correct_id == self.last_received_id[obj.nick]:
    
    1076
    -                    if obj.nick == self.nick:
    
    1077
    -                        old_txt = self.last_sent_txt
    
    1078
    -                        self.last_sent_txt = obj.msgtxt
    
    1079
    -                        self.conv_textview.correct_last_sent_message(obj.msgtxt,
    
    1080
    -                            obj.xhtml_msgtxt, obj.nick, old_txt)
    
    1081
    -                    else:
    
    1082
    -                        old_txt = self.last_received_txt[obj.nick]
    
    1083
    -                        (highlight, sound) = self.highlighting_for_message(obj.msgtxt, obj.timestamp)
    
    1084
    -                        other_tags_for_name = []
    
    1085
    -                        other_tags_for_text = []
    
    1086
    -                        if obj.nick in self.gc_custom_colors:
    
    1087
    -                            other_tags_for_name.append('gc_nickname_color_' + \
    
    1088
    -                                str(self.gc_custom_colors[obj.nick]))
    
    1089
    -                        else:
    
    1090
    -                            self.gc_count_nicknames_colors += 1
    
    1091
    -                            if self.gc_count_nicknames_colors == \
    
    1092
    -                            self.number_of_colors:
    
    1093
    -                                self.gc_count_nicknames_colors = 0
    
    1094
    -                            self.gc_custom_colors[obj.nick] = \
    
    1095
    -                                self.gc_count_nicknames_colors
    
    1096
    -                            other_tags_for_name.append('gc_nickname_color_' + \
    
    1097
    -                                str(self.gc_count_nicknames_colors))
    
    1098
    -                        if highlight:
    
    1099
    -                            # muc-specific chatstate
    
    1100
    -                            if self.parent_win:
    
    1101
    -                                self.parent_win.redraw_tab(self, 'attention')
    
    1102
    -                            else:
    
    1103
    -                                self.attention_flag = True
    
    1104
    -                            other_tags_for_name.append('bold')
    
    1105
    -                            other_tags_for_text.append('marked')
    
    1106
    -
    
    1107
    -                            if obj.nick in self.attention_list:
    
    1108
    -                                self.attention_list.remove(obj.nick)
    
    1109
    -                            elif len(self.attention_list) > 6:
    
    1110
    -                                self.attention_list.pop(0) # remove older
    
    1111
    -                            self.attention_list.append(obj.nick)
    
    1112
    -
    
    1113
    -                        if obj.msgtxt.startswith('/me ') or \
    
    1114
    -                        obj.msgtxt.startswith('/me\n'):
    
    1115
    -                            other_tags_for_text.append('gc_nickname_color_' + \
    
    1116
    -                                str(self.gc_custom_colors[obj.nick]))
    
    1117
    -                        self.conv_textview.correct_last_received_message(
    
    1118
    -                            obj.msgtxt, obj.xhtml_msgtxt, obj.nick, old_txt,
    
    1119
    -                            other_tags_for_name=other_tags_for_name,
    
    1120
    -                            other_tags_for_text=other_tags_for_text)
    
    1121
    -                    self.last_received_txt[obj.nick] = obj.msgtxt
    
    1122
    -                    self.last_received_id[obj.nick] = obj.stanza.getID()
    
    1123
    -                    return
    
    1124 1074
                     if obj.nick == self.nick:
    
    1125 1075
                         self.last_sent_txt = obj.msgtxt
    
    1126 1076
                     self.print_conversation(obj.msgtxt, contact=obj.nick,
    
    1127 1077
                         tim=obj.timestamp, xhtml=obj.xhtml_msgtxt,
    
    1128 1078
                         displaymarking=obj.displaymarking,
    
    1129
    -                    correct_id=(obj.stanza.getID(), None))
    
    1079
    +                    correct_id=obj.correct_id, msg_stanza_id=obj.id_)
    
    1130 1080
             obj.needs_highlight = self.needs_visual_notification(obj.msgtxt)
    
    1131 1081
     
    
    1132 1082
         def on_private_message(self, nick, msg, tim, xhtml, session, msg_log_id=None,
    
    ... ... @@ -1181,7 +1131,7 @@ class GroupchatControl(ChatControlBase):
    1181 1131
             return None
    
    1182 1132
     
    
    1183 1133
         def print_old_conversation(self, text, contact='', tim=None, xhtml = None,
    
    1184
    -    displaymarking=None):
    
    1134
    +    displaymarking=None, msg_stanza_id=None):
    
    1185 1135
             if contact:
    
    1186 1136
                 if contact == self.nick: # it's us
    
    1187 1137
                     kind = 'outgoing'
    
    ... ... @@ -1196,10 +1146,10 @@ class GroupchatControl(ChatControlBase):
    1196 1146
             ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
    
    1197 1147
                 small_attr, small_attr + ['restored_message'],
    
    1198 1148
                 small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml,
    
    1199
    -            displaymarking=displaymarking)
    
    1149
    +            displaymarking=displaymarking, msg_stanza_id=msg_stanza_id)
    
    1200 1150
     
    
    1201 1151
         def print_conversation(self, text, contact='', tim=None, xhtml=None,
    
    1202
    -    graphics=True, displaymarking=None, correct_id=None):
    
    1152
    +    graphics=True, displaymarking=None, correct_id=None, msg_stanza_id=None):
    
    1203 1153
             """
    
    1204 1154
             Print a line in the conversation
    
    1205 1155
     
    
    ... ... @@ -1261,7 +1211,7 @@ class GroupchatControl(ChatControlBase):
    1261 1211
             ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
    
    1262 1212
                 other_tags_for_name, [], other_tags_for_text, xhtml=xhtml,
    
    1263 1213
                 graphics=graphics, displaymarking=displaymarking,
    
    1264
    -            correct_id=correct_id)
    
    1214
    +            correct_id=correct_id, msg_stanza_id=msg_stanza_id)
    
    1265 1215
     
    
    1266 1216
         def get_nb_unread(self):
    
    1267 1217
             type_events = ['printed_marked_gc_msg']
    
    ... ... @@ -1422,8 +1372,8 @@ class GroupchatControl(ChatControlBase):
    1422 1372
                     # print if a control is open
    
    1423 1373
                     obj.session.control.print_conversation(obj.msgtxt,
    
    1424 1374
                         tim=obj.timestamp, xhtml=obj.xhtml, encrypted=obj.encrypted,
    
    1425
    -                    displaymarking=obj.displaymarking, correct_id=(obj.id_,
    
    1426
    -                    obj.correct_id))
    
    1375
    +                    displaymarking=obj.displaymarking, msg_stanza_id=obj.id_,
    
    1376
    +                    correct_id=obj.correct_id)
    
    1427 1377
                 else:
    
    1428 1378
                     # otherwise pass it off to the control to be queued
    
    1429 1379
                     self.on_private_message(nick, obj.msgtxt, obj.timestamp,
    

  • src/roster_window.py
    ... ... @@ -1858,7 +1858,7 @@ class RosterWindow:
    1858 1858
                     # with them
    
    1859 1859
                     session = gajim.connections[account].make_new_session(jid)
    
    1860 1860
     
    
    1861
    -                tim = time.localtime(float(result[2]))
    
    1861
    +                tim = float(result[2])
    
    1862 1862
                     session.roster_message(jid, result[1], tim, msg_type='chat',
    
    1863 1863
                         msg_log_id=result[0], additional_data=additional_data)
    
    1864 1864
                     gajim.logger.set_shown_unread_msgs(result[0])
    
    ... ... @@ -2744,7 +2744,7 @@ class RosterWindow:
    2744 2744
                 obj.session.control.print_conversation(obj.msgtxt, typ,
    
    2745 2745
                     tim=obj.timestamp, encrypted=obj.encrypted, subject=obj.subject,
    
    2746 2746
                     xhtml=obj.xhtml, displaymarking=obj.displaymarking,
    
    2747
    -                msg_log_id=obj.msg_log_id, correct_id=(obj.id_, obj.correct_id),
    
    2747
    +                msg_log_id=obj.msg_log_id, msg_stanza_id=obj.id_, correct_id=obj.correct_id,
    
    2748 2748
                     xep0184_id=xep0184_id)
    
    2749 2749
                 if obj.msg_log_id:
    
    2750 2750
                     pw = obj.session.control.parent_win