Daniel Brötzmann pushed to branch newcontrol at gajim / gajim

Commits:

11 changed files:

Changes:

  • gajim/common/storage/archive.py
    ... ... @@ -992,6 +992,35 @@ def find_stanza_id(self,
    992 992
                 return True
    
    993 993
             return False
    
    994 994
     
    
    995
    +    @timeit
    
    996
    +    def get_last_correctable_message(self,
    
    997
    +                                     account: str,
    
    998
    +                                     jid: JID,
    
    999
    +                                     message_id: str
    
    1000
    +                                     ) -> Optional[LastConversationRow]:
    
    1001
    +        """
    
    1002
    +        Load the last correctable message of a conversation by message_id.
    
    1003
    +        Conditions: max 5 min old
    
    1004
    +        """
    
    1005
    +        jids = [jid]
    
    1006
    +        account_id = self.get_account_id(account)
    
    1007
    +        min_time = time.time() - 5 * 60
    
    1008
    +        print(min_time)
    
    1009
    +
    
    1010
    +        sql = '''
    
    1011
    +            SELECT contact_name, time, kind, message, stanza_id, message_id,
    
    1012
    +            additional_data
    
    1013
    +            FROM logs NATURAL JOIN jids WHERE jid IN ({jids})
    
    1014
    +            AND account_id = {account_id}
    
    1015
    +            AND message_id = ?
    
    1016
    +            AND time > ?
    
    1017
    +            '''.format(jids=', '.join('?' * len(jids)),
    
    1018
    +                       account_id=account_id)
    
    1019
    +
    
    1020
    +        return self._con.execute(
    
    1021
    +            sql,
    
    1022
    +            tuple(jids) + (message_id, min_time)).fetchone()
    
    1023
    +
    
    995 1024
         @timeit
    
    996 1025
         def store_message_correction(self,
    
    997 1026
                                      account: str,
    

  • gajim/gtk/chat_stack.py
    ... ... @@ -306,6 +306,8 @@ def _update_base_actions(self, contact: ChatContactT) -> None:
    306 306
                 httpupload.get_enabled())
    
    307 307
     
    
    308 308
             app.window.get_action('show-contact-info').set_enabled(online)
    
    309
    +        app.window.get_action('correct-message').set_enabled(online)
    
    310
    +
    
    309 311
             app.window.get_action('input-bold').set_enabled(True)
    
    310 312
             app.window.get_action('input-italic').set_enabled(True)
    
    311 313
             app.window.get_action('input-strike').set_enabled(True)
    
    ... ... @@ -538,11 +540,11 @@ def _on_send_message(self) -> None:
    538 540
     
    
    539 541
             label = self._message_action_box.get_seclabel()
    
    540 542
     
    
    541
    -        correct_id: Optional[str] = None
    
    542
    -        if control.correcting and control.last_sent_msg:
    
    543
    -            correct_id = control.last_sent_msg
    
    544
    -        else:
    
    545
    -            correct_id = None
    
    543
    +        correct_id = None
    
    544
    +        if self._message_action_box.is_correcting:
    
    545
    +            correct_id = self._message_action_box.last_message_id.get(
    
    546
    +                (contact.account, contact.jid))
    
    547
    +            self._message_action_box.is_correcting = False
    
    546 548
     
    
    547 549
             chatstate = client.get_module('Chatstate').get_active_chatstate(
    
    548 550
                 contact)
    
    ... ... @@ -563,8 +565,9 @@ def _on_send_message(self) -> None:
    563 565
             client.send_message(message_)
    
    564 566
     
    
    565 567
             self._message_action_box.msg_textview.clear()
    
    566
    -        self._message_action_box.msg_textview.get_style_context().remove_class(
    
    567
    -            'gajim-msg-correcting')
    
    568
    +
    
    569
    +    def get_last_message_id(self, account: str, jid: JID) -> Optional[str]:
    
    570
    +        return self._message_action_box.last_message_id.get((account, jid))
    
    568 571
     
    
    569 572
         def _close_control(self) -> None:
    
    570 573
             assert self._current_contact is not None
    
    ... ... @@ -582,6 +585,7 @@ def process_event(self, event: Any) -> None:
    582 585
             if isinstance(event, MucDiscoUpdate):
    
    583 586
                 self._on_muc_disco_update(event)
    
    584 587
             self._control_stack.process_event(event)
    
    588
    +        self._message_action_box.process_event(event)
    
    585 589
     
    
    586 590
     
    
    587 591
     class ChatPlaceholderBox(Gtk.Box):
    

  • gajim/gtk/const.py
    ... ... @@ -194,6 +194,7 @@ def __str__(self):
    194 194
         ('input-strike', None, False),
    
    195 195
         ('input-clear', None, False),
    
    196 196
         ('show-emoji-chooser', None, False),
    
    197
    +    ('correct-message', None, False),
    
    197 198
         ('quote', 's', False),
    
    198 199
         ('mention', 's', False),
    
    199 200
         ('send-file-httpupload', None, False),
    

  • gajim/gtk/controls/base.py
    ... ... @@ -182,15 +182,10 @@ def __init__(self, widget_name: str, account: str, jid: JID) -> None:
    182 182
             # XEP-0333 Chat Markers
    
    183 183
             self.last_msg_id: Optional[str] = None
    
    184 184
     
    
    185
    -        # XEP-0308 Message Correction
    
    186
    -        self.last_sent_msg: Optional[str] = None
    
    187
    -
    
    188 185
             # XEP-0172 User Nickname
    
    189 186
             # TODO:
    
    190 187
             self.user_nick: Optional[str] = None
    
    191 188
     
    
    192
    -        self.sendmessage: bool = True
    
    193
    -
    
    194 189
             self._client.get_module('Chatstate').set_active(self.contact)
    
    195 190
     
    
    196 191
             self.encryption: Optional[str] = self.get_encryption_state()
    
    ... ... @@ -501,11 +496,8 @@ def add_message(self,
    501 496
                     self.last_msg_id = message_id
    
    502 497
     
    
    503 498
             if kind == 'incoming':
    
    504
    -            # Record the history of received messages
    
    505
    -            # self.save_message(text, 'received')
    
    506
    -
    
    507
    -            # Issue notification
    
    508 499
                 if notify:
    
    500
    +                # Issue notification
    
    509 501
                     self._notify(name, text, tim, additional_data)
    
    510 502
     
    
    511 503
                 if not chat_active and notify:
    

  • gajim/gtk/controls/chat.py
    ... ... @@ -70,12 +70,6 @@ def __init__(self, account: str, jid: JID) -> None:
    70 70
                                  'chat_control',
    
    71 71
                                  account,
    
    72 72
                                  jid)
    
    73
    -
    
    74
    -        self.sendmessage: bool = True
    
    75
    -
    
    76
    -        # XEP-0308 Message Correction
    
    77
    -        self.last_sent_msg: Optional[str] = None
    
    78
    -
    
    79 73
             # PluginSystem: adding GUI extension point for this ChatControl
    
    80 74
             # instance object
    
    81 75
             app.plugin_manager.gui_extension_point('chat_control', self)
    
    ... ... @@ -132,13 +126,6 @@ def _on_message_sent(self, event: events.MessageSent) -> None:
    132 126
             if not event.message:
    
    133 127
                 return
    
    134 128
     
    
    135
    -        if event.correct_id is None:
    
    136
    -            oob_url = event.additional_data.get_value('gajim', 'oob_url')
    
    137
    -            if oob_url == event.message:
    
    138
    -                self.last_sent_msg = None
    
    139
    -            else:
    
    140
    -                self.last_sent_msg = event.message_id
    
    141
    -
    
    142 129
             message_id = event.message_id
    
    143 130
     
    
    144 131
             if event.label:
    

  • gajim/gtk/controls/groupchat.py
    ... ... @@ -82,15 +82,6 @@ def __init__(self, account: str, jid: JID) -> None:
    82 82
     
    
    83 83
             self.room_jid = str(self.contact.jid)
    
    84 84
     
    
    85
    -        # Last sent message text
    
    86
    -        self.last_sent_txt: str = ''
    
    87
    -
    
    88
    -        # Attribute, encryption plugins use to signal the message can be sent
    
    89
    -        self.sendmessage: bool = False
    
    90
    -
    
    91
    -        # XEP-0308 Message Correction
    
    92
    -        self.last_sent_msg: Optional[str] = None
    
    93
    -
    
    94 85
             self._groupchat_state = GroupchatState(self.contact)
    
    95 86
             self.xml.conv_view_overlay.add_overlay(self._groupchat_state)
    
    96 87
     
    
    ... ... @@ -226,9 +217,6 @@ def _on_gc_message_received(self, event: events.GcMessageReceived) -> None:
    226 217
                                  displaymarking=event.displaymarking,
    
    227 218
                                  additional_data=event.additional_data)
    
    228 219
             else:
    
    229
    -            if event.properties.muc_nickname == self.contact.nickname:
    
    230
    -                self.last_sent_txt = event.msgtxt
    
    231
    -
    
    232 220
                 self.add_message(event.msgtxt,
    
    233 221
                                  contact=event.properties.muc_nickname,
    
    234 222
                                  tim=event.properties.timestamp,
    
    ... ... @@ -634,16 +622,7 @@ def _on_room_destroyed(self,
    634 622
                 self.add_info_message(join_message)
    
    635 623
     
    
    636 624
         def _on_message_sent(self, event: events.MessageSent) -> None:
    
    637
    -        if not event.message:
    
    638
    -            return
    
    639
    -        # we'll save sent message text when we'll receive it in
    
    640
    -        # _nec_gc_message_received
    
    641
    -        if event.correct_id is None:
    
    642
    -            oob_url = event.additional_data.get_value('gajim', 'oob_url')
    
    643
    -            if oob_url == event.message:
    
    644
    -                self.last_sent_msg = None
    
    645
    -            else:
    
    646
    -                self.last_sent_msg = event.message_id
    
    625
    +        pass
    
    647 626
     
    
    648 627
         def _on_message_error(self, event: events.MessageError) -> None:
    
    649 628
             self.conversation_view.show_error(event.message_id, event.error)
    

  • gajim/gtk/conversation/rows/message.py
    ... ... @@ -319,6 +319,9 @@ def on_quote_message(self, _widget: Gtk.Widget) -> None:
    319 319
             app.window.activate_action(
    
    320 320
                 'quote', GLib.Variant('s', self._message_widget.get_text()))
    
    321 321
     
    
    322
    +    def on_correct_message(self, _widget: Gtk.Widget) -> None:
    
    323
    +        app.window.activate_action('correct-message', None)
    
    324
    +
    
    322 325
         def on_retract_message(self, _widget: Gtk.Widget) -> None:
    
    323 326
             def _on_retract(reason: str) -> None:
    
    324 327
                 self._client.get_module('MUC').retract_message(
    

  • gajim/gtk/conversation/rows/widgets.py
    ... ... @@ -14,8 +14,7 @@
    14 14
     
    
    15 15
     from __future__ import annotations
    
    16 16
     
    
    17
    -from typing import Union
    
    18
    -from typing import Any
    
    17
    +from typing import TYPE_CHECKING
    
    19 18
     
    
    20 19
     from gi.repository import Gtk
    
    21 20
     from gi.repository import Pango
    
    ... ... @@ -24,13 +23,13 @@
    24 23
     from gajim.common import app
    
    25 24
     from gajim.common.i18n import _
    
    26 25
     from gajim.common.helpers import is_retraction_allowed
    
    27
    -from gajim.common.modules.contacts import BareContact
    
    28 26
     from gajim.common.modules.contacts import GroupchatContact
    
    29
    -from gajim.common.modules.contacts import GroupchatParticipant
    
    27
    +from gajim.common.types import ChatContactT
    
    30 28
     
    
    31
    -from ...util import wrap_with_event_box
    
    29
    +if TYPE_CHECKING:
    
    30
    +    from .message import MessageRow
    
    32 31
     
    
    33
    -ContactT = Union[BareContact, GroupchatContact, GroupchatParticipant]
    
    32
    +from ...util import wrap_with_event_box
    
    34 33
     
    
    35 34
     
    
    36 35
     class SimpleLabel(Gtk.Label):
    
    ... ... @@ -45,8 +44,8 @@ def __init__(self) -> None:
    45 44
     @wrap_with_event_box
    
    46 45
     class MoreMenuButton(Gtk.Button):
    
    47 46
         def __init__(self,
    
    48
    -                 row: Any,
    
    49
    -                 contact: ContactT,
    
    47
    +                 row: MessageRow,
    
    48
    +                 contact: ChatContactT,
    
    50 49
                      name: str
    
    51 50
                      ) -> None:
    
    52 51
     
    
    ... ... @@ -70,23 +69,33 @@ def _on_click(self, _button: Gtk.Button) -> None:
    70 69
             show_retract = False
    
    71 70
             if isinstance(self._contact, GroupchatContact):
    
    72 71
                 if not self._contact.is_joined:
    
    73
    -                self._create_popover(False)
    
    72
    +                self._create_popover(show_retract=False)
    
    74 73
                     return
    
    75 74
     
    
    76
    -            disco_info = app.storage.cache.get_last_disco_info(
    
    77
    -                self._contact.jid)
    
    78
    -            assert disco_info is not None
    
    79
    -
    
    80 75
                 contact = self._contact.get_resource(self._name)
    
    81 76
                 self_contact = self._contact.get_self()
    
    82 77
                 assert self_contact is not None
    
    83
    -
    
    84 78
                 is_allowed = is_retraction_allowed(self_contact, contact)
    
    79
    +
    
    80
    +            disco_info = app.storage.cache.get_last_disco_info(
    
    81
    +                self._contact.jid)
    
    82
    +            assert disco_info is not None
    
    83
    +
    
    85 84
                 if disco_info.has_message_moderation and is_allowed:
    
    86 85
                     show_retract = True
    
    87
    -        self._create_popover(show_retract)
    
    88 86
     
    
    89
    -    def _create_popover(self, show_retract: bool) -> None:
    
    87
    +        show_correction = False
    
    88
    +        if self._row.message_id is not None:
    
    89
    +            show_correction = app.window.is_message_correctable(
    
    90
    +                self._contact.account, self._contact.jid, self._row.message_id)
    
    91
    +
    
    92
    +        self._create_popover(show_retract=show_retract,
    
    93
    +                             show_correction=show_correction)
    
    94
    +
    
    95
    +    def _create_popover(self,
    
    96
    +                        show_retract: bool = False,
    
    97
    +                        show_correction: bool = False
    
    98
    +                        ) -> None:
    
    90 99
             menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
    
    91 100
             menu_box.get_style_context().add_class('padding-6')
    
    92 101
     
    
    ... ... @@ -116,6 +125,16 @@ def _create_popover(self, show_retract: bool) -> None:
    116 125
                 'edit-copy-symbolic', Gtk.IconSize.MENU))
    
    117 126
             menu_box.add(copy_button)
    
    118 127
     
    
    128
    +        if show_correction:
    
    129
    +            correct_button = Gtk.ModelButton()
    
    130
    +            correct_button.set_halign(Gtk.Align.START)
    
    131
    +            correct_button.connect(
    
    132
    +                'clicked', self._row.on_correct_message)
    
    133
    +            correct_button.set_label(_('Correct'))
    
    134
    +            correct_button.set_image(Gtk.Image.new_from_icon_name(
    
    135
    +                'document-edit-symbolic', Gtk.IconSize.MENU))
    
    136
    +            menu_box.add(correct_button)
    
    137
    +
    
    119 138
             if show_retract:
    
    120 139
                 retract_button = Gtk.ModelButton()
    
    121 140
                 retract_button.set_halign(Gtk.Align.START)
    

  • gajim/gtk/conversation/view.py
    ... ... @@ -380,7 +380,7 @@ def _reduce_messages_before(self) -> bool:
    380 380
             row1 = self.get_row_at_index(2)
    
    381 381
             row2 = self.get_row_at_index(3)
    
    382 382
     
    
    383
    -        if row1.type == row2.type == 'date':
    
    383
    +        if row1.type == 'date' and row2.type == 'date':
    
    384 384
                 # First two rows are date rows,
    
    385 385
                 # it’s safe to remove the fist row
    
    386 386
                 row1.destroy()
    

  • gajim/gtk/main.py
    ... ... @@ -38,7 +38,8 @@
    38 38
     from gajim.common.ged import EventHelper
    
    39 39
     from gajim.common.i18n import _
    
    40 40
     from gajim.common.modules.bytestream import is_transfer_active
    
    41
    -from gajim.gtk.const import MAIN_WIN_ACTIONS, MAIN_WIN_STATEFUL_ACTIONS
    
    41
    +from gajim.gtk.const import MAIN_WIN_ACTIONS
    
    42
    +from gajim.gtk.const import MAIN_WIN_STATEFUL_ACTIONS
    
    42 43
     from gajim.plugins.pluginmanager import PluginManifest
    
    43 44
     from gajim.plugins.repository import PluginRepository
    
    44 45
     
    
    ... ... @@ -731,6 +732,20 @@ def is_chat_loaded(self, account: str, jid: JID) -> bool:
    731 732
         def chat_exists(self, account: str, jid: JID) -> bool:
    
    732 733
             return self._chat_page.chat_exists(account, jid)
    
    733 734
     
    
    735
    +    def is_message_correctable(self,
    
    736
    +                               account: str,
    
    737
    +                               jid: JID,
    
    738
    +                               message_id: str
    
    739
    +                               ) -> bool:
    
    740
    +        chat_stack = self._chat_page.get_chat_stack()
    
    741
    +        last_message_id = chat_stack.get_last_message_id(account, jid)
    
    742
    +        if last_message_id is None or last_message_id != message_id:
    
    743
    +            return False
    
    744
    +
    
    745
    +        message_row = app.storage.archive.get_last_correctable_message(
    
    746
    +            account, jid, last_message_id)
    
    747
    +        return message_row is not None
    
    748
    +
    
    734 749
         def get_total_unread_count(self) -> int:
    
    735 750
             chat_list_stack = self._chat_page.get_chat_list_stack()
    
    736 751
             return chat_list_stack.get_total_unread_count()
    

  • gajim/gtk/message_actions_box.py
    ... ... @@ -32,8 +32,10 @@
    32 32
     
    
    33 33
     from nbxmpp.const import Chatstate
    
    34 34
     from nbxmpp.modules.security_labels import SecurityLabel
    
    35
    +from nbxmpp.protocol import JID
    
    35 36
     
    
    36 37
     from gajim.common import app
    
    38
    +from gajim.common import events
    
    37 39
     from gajim.common import i18n
    
    38 40
     from gajim.common.i18n import _
    
    39 41
     from gajim.common.client import Client
    
    ... ... @@ -65,7 +67,7 @@
    65 67
     
    
    66 68
     class MessageActionsBox(Gtk.Grid):
    
    67 69
         def __init__(self) -> None:
    
    68
    -        super().__init__()
    
    70
    +        Gtk.Grid.__init__(self)
    
    69 71
     
    
    70 72
             self._client: Optional[Client] = None
    
    71 73
             self._contact: Optional[ChatContactT] = None
    
    ... ... @@ -75,8 +77,13 @@ def __init__(self) -> None:
    75 77
     
    
    76 78
             self.attach(self._ui.box, 0, 0, 1, 1)
    
    77 79
     
    
    80
    +        # For undo
    
    78 81
             self.space_pressed = False
    
    79 82
     
    
    83
    +        # XEP-0308 Message Correction
    
    84
    +        self.is_correcting = False
    
    85
    +        self.last_message_id: dict[tuple[str, JID], Optional[str]] = {}
    
    86
    +
    
    80 87
             self._ui.send_message_button.set_visible(
    
    81 88
                 app.settings.get('show_send_message_button'))
    
    82 89
             app.settings.bind_signal('show_send_message_button',
    
    ... ... @@ -90,7 +97,7 @@ def __init__(self) -> None:
    90 97
             self.msg_textview.get_buffer().connect('changed',
    
    91 98
                                                    self._on_buffer_changed)
    
    92 99
             self.msg_textview.connect('key-press-event',
    
    93
    -                                  self._on_textview_key_press_event)
    
    100
    +                                  self._on_msg_textview_key_press_event)
    
    94 101
             self.msg_textview.connect('paste-clipboard',
    
    95 102
                                       self._on_paste_clipboard)
    
    96 103
     
    
    ... ... @@ -133,6 +140,7 @@ def _connect_actions(self) -> None:
    133 140
                 'show-emoji-chooser',
    
    134 141
                 'quote',
    
    135 142
                 'mention',
    
    143
    +            'correct-message',
    
    136 144
             ]
    
    137 145
     
    
    138 146
             for action in actions:
    
    ... ... @@ -179,6 +187,9 @@ def _on_action(self,
    179 187
                 assert param
    
    180 188
                 self.msg_textview.mention_participant(param.get_string())
    
    181 189
     
    
    190
    +        elif action_name == 'correct-message':
    
    191
    +            self.toggle_message_correction()
    
    192
    +
    
    182 193
         def switch_contact(self, contact: ChatContactT) -> None:
    
    183 194
             if self._client is not None:
    
    184 195
                 self._set_chatstate(False)
    
    ... ... @@ -503,10 +514,10 @@ def _on_buffer_changed(self, textbuffer: Gtk.TextBuffer) -> None:
    503 514
             client.get_module('Chatstate').set_chatstate(
    
    504 515
                 self._contact, Chatstate.COMPOSING)
    
    505 516
     
    
    506
    -    def _on_textview_key_press_event(self,
    
    507
    -                                     textview: MessageInputTextView,
    
    508
    -                                     event: Gdk.EventKey
    
    509
    -                                     ) -> bool:
    
    517
    +    def _on_msg_textview_key_press_event(self,
    
    518
    +                                         textview: MessageInputTextView,
    
    519
    +                                         event: Gdk.EventKey
    
    520
    +                                         ) -> bool:
    
    510 521
             # pylint: disable=too-many-nested-blocks
    
    511 522
             if event.keyval == Gdk.KEY_space:
    
    512 523
                 self.space_pressed = True
    
    ... ... @@ -525,24 +536,33 @@ def _on_textview_key_press_event(self,
    525 536
                                                              True))
    
    526 537
                 self.space_pressed = False
    
    527 538
     
    
    539
    +        event_state = event.get_state()
    
    528 540
             # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here
    
    529
    -        if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
    
    530
    -            if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and
    
    531
    -                    event.keyval == Gdk.KEY_ISO_Left_Tab):
    
    532
    -                app.window.select_next_chat(Direction.PREV, unread_first=True)
    
    533
    -                return True
    
    541
    +        if event_state & Gdk.ModifierType.SHIFT_MASK:
    
    542
    +            if event_state & Gdk.ModifierType.CONTROL_MASK:
    
    543
    +                if event.keyval == Gdk.KEY_ISO_Left_Tab:
    
    544
    +                    app.window.select_next_chat(
    
    545
    +                        Direction.PREV, unread_first=True)
    
    546
    +                    return True
    
    534 547
     
    
    535 548
                 if event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up):
    
    536 549
                     self.conversation_view.event(event)
    
    537 550
                     return True
    
    538 551
     
    
    539
    -        if event.get_state() & Gdk.ModifierType.CONTROL_MASK:
    
    552
    +        if event_state & Gdk.ModifierType.CONTROL_MASK:
    
    540 553
                 if event.keyval == Gdk.KEY_Tab:
    
    541 554
                     app.window.select_next_chat(Direction.NEXT, unread_first=True)
    
    542 555
                     return True
    
    543 556
     
    
    544
    -        event_state = event.get_state()
    
    545
    -        if event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):  # ENTER
    
    557
    +            if event.keyval == Gdk.KEY_z:
    
    558
    +                self.msg_textview.undo()
    
    559
    +                return True
    
    560
    +
    
    561
    +            if event.keyval == Gdk.KEY_Up:
    
    562
    +                self.toggle_message_correction()
    
    563
    +                return True
    
    564
    +
    
    565
    +        if event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
    
    546 566
                 if event_state & Gdk.ModifierType.SHIFT_MASK:
    
    547 567
                     textview.insert_newline()
    
    548 568
                     return True
    
    ... ... @@ -566,11 +586,6 @@ def _on_textview_key_press_event(self,
    566 586
                 app.window.activate_action('send-message', None)
    
    567 587
                 return True
    
    568 588
     
    
    569
    -        if event.keyval == Gdk.KEY_z:  # CTRL+z
    
    570
    -            if event_state & Gdk.ModifierType.CONTROL_MASK:
    
    571
    -                self.msg_textview.undo()
    
    572
    -                return True
    
    573
    -
    
    574 589
             return False
    
    575 590
     
    
    576 591
         def _on_paste_clipboard(self,
    
    ... ... @@ -616,3 +631,52 @@ def _paste_event_confirmed(self,
    616 631
     
    
    617 632
             assert self._contact is not None
    
    618 633
             app.interface.start_file_transfer(self._contact, path)
    
    634
    +
    
    635
    +    def toggle_message_correction(self) -> None:
    
    636
    +        if self.is_correcting:
    
    637
    +            self.is_correcting = False
    
    638
    +            self.msg_textview.clear()
    
    639
    +            self.msg_textview.get_style_context().remove_class(
    
    640
    +                'gajim-msg-correcting')
    
    641
    +            self.msg_textview.grab_focus()
    
    642
    +            return
    
    643
    +
    
    644
    +        assert self._contact is not None
    
    645
    +        last_message_id = self.last_message_id.get(
    
    646
    +            (self._contact.account, self._contact.jid))
    
    647
    +        if last_message_id is None:
    
    648
    +            return
    
    649
    +
    
    650
    +        message_row = app.storage.archive.get_last_correctable_message(
    
    651
    +            self._contact.account, self._contact.jid, last_message_id)
    
    652
    +        if message_row is None:
    
    653
    +            return
    
    654
    +
    
    655
    +        self.is_correcting = True
    
    656
    +        self.msg_textview.clear()
    
    657
    +        self.msg_textview.insert_text(message_row.message)
    
    658
    +        self.msg_textview.get_style_context().add_class('gajim-msg-correcting')
    
    659
    +        self.msg_textview.grab_focus()
    
    660
    +
    
    661
    +    def _on_message_sent(self, event: events.MessageSent) -> None:
    
    662
    +        if not event.message:
    
    663
    +            return
    
    664
    +
    
    665
    +        if event.correct_id is None:
    
    666
    +            # This wasn't a corrected message
    
    667
    +            assert self._contact is not None
    
    668
    +            oob_url = event.additional_data.get_value('gajim', 'oob_url')
    
    669
    +            account = self._contact.account
    
    670
    +            jid = self._contact.jid
    
    671
    +            if oob_url == event.message:
    
    672
    +                # Don't allow to correct HTTP Upload file transfer URLs
    
    673
    +                self.last_message_id[(account, jid)] = None
    
    674
    +            else:
    
    675
    +                self.last_message_id[(account, jid)] = event.message_id
    
    676
    +
    
    677
    +        self.msg_textview.get_style_context().remove_class(
    
    678
    +            'gajim-msg-correcting')
    
    679
    +
    
    680
    +    def process_event(self, event: events.ApplicationEvent) -> None:
    
    681
    +        if isinstance(event, events.MessageSent):
    
    682
    +            self._on_message_sent(event)