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

Commits:

12 changed files:

Changes:

  • gajim/common/helpers.py
    ... ... @@ -64,6 +64,7 @@
    64 64
     
    
    65 65
     from nbxmpp.namespaces import Namespace
    
    66 66
     from nbxmpp.const import Role
    
    67
    +from nbxmpp.const import Chatstate
    
    67 68
     from nbxmpp.const import ConnectionProtocol
    
    68 69
     from nbxmpp.const import ConnectionType
    
    69 70
     from nbxmpp.const import Affiliation
    
    ... ... @@ -304,17 +305,23 @@ def from_one_line(msg: str) -> str:
    304 305
         return msg
    
    305 306
     
    
    306 307
     
    
    307
    -def get_uf_chatstate(chatstate: str) -> str:
    
    308
    -    '''
    
    309
    -    Remove chatstate jargon and returns user friendly messages
    
    310
    -    '''
    
    311
    -    if chatstate in ('inactive', 'gone'):
    
    312
    -        return _('is doing something else')
    
    313
    -    if chatstate == 'composing':
    
    308
    +def chatstate_to_string(chatstate: Optional[Chatstate]) -> str:
    
    309
    +    if chatstate is None:
    
    310
    +        return ''
    
    311
    +
    
    312
    +    if chatstate == Chatstate.ACTIVE:
    
    313
    +        return ''
    
    314
    +
    
    315
    +    if chatstate == Chatstate.COMPOSING:
    
    314 316
             return _('is composing a message…')
    
    315
    -    if chatstate == 'paused':
    
    317
    +
    
    318
    +    if chatstate in (Chatstate.INACTIVE, Chatstate.GONE):
    
    319
    +        return _('is doing something else')
    
    320
    +
    
    321
    +    if chatstate == Chatstate.PAUSED:
    
    316 322
             return _('paused composing a message')
    
    317
    -    return ''
    
    323
    +
    
    324
    +    raise ValueError('unknown value: %s' % chatstate)
    
    318 325
     
    
    319 326
     
    
    320 327
     def exec_command(command: str,
    

  • gajim/common/modules/contacts.py
    ... ... @@ -45,6 +45,7 @@
    45 45
     from gajim.common.structs import UNKNOWN_MUC_PRESENCE
    
    46 46
     from gajim.common.structs import MUCPresenceData
    
    47 47
     from gajim.common.helpers import Observable
    
    48
    +from gajim.common.helpers import chatstate_to_string
    
    48 49
     from gajim.common.modules.base import BaseModule
    
    49 50
     from gajim.common.modules.util import LogAdapter
    
    50 51
     from gajim.common.helpers import get_groupchat_name
    
    ... ... @@ -322,6 +323,14 @@ def is_jingle_available(self) -> bool:
    322 323
         def get_address(self, _prefer_real: bool = True) -> JID:
    
    323 324
             return self._jid
    
    324 325
     
    
    326
    +    @property
    
    327
    +    def chatstate(self) -> Optional[Chatstate]:
    
    328
    +        return None
    
    329
    +
    
    330
    +    @property
    
    331
    +    def chatstate_string(self) -> str:
    
    332
    +        return chatstate_to_string(self.chatstate)
    
    333
    +
    
    325 334
         def __repr__(self) -> str:
    
    326 335
             return f'{self.jid} ({self._account})'
    
    327 336
     
    

  • gajim/common/preview.py
    ... ... @@ -237,8 +237,13 @@ def _accept_uri(urlparts: ParseResult,
    237 237
             if urlparts.scheme in ('https', 'http'):
    
    238 238
                 if app.settings.get('preview_allow_all_images'):
    
    239 239
                     mime_type = guess_mime_type(uri)
    
    240
    -                if (mime_type not in MIME_TYPES or
    
    241
    -                        mime_type == 'application/octet-stream'):
    
    240
    +                if mime_type not in MIME_TYPES:
    
    241
    +                    return False
    
    242
    +
    
    243
    +                if mime_type == 'application/octet-stream' and uri != oob_url:
    
    244
    +                    # guess_mime_type yields 'application/octet-stream' for
    
    245
    +                    # paths without suffix. Check oob_url to make sure we
    
    246
    +                    # display a preview for files sent via http upload.
    
    242 247
                         return False
    
    243 248
                     return True
    
    244 249
     
    
    ... ... @@ -345,7 +350,7 @@ def _on_orig_load_finished(self,
    345 350
                 log.error('%s: %s', preview.orig_path.name, error)
    
    346 351
                 return
    
    347 352
     
    
    348
    -        preview.mime_type = guess_mime_type(preview.orig_path)
    
    353
    +        preview.mime_type = guess_mime_type(preview.orig_path, data)
    
    349 354
             preview.file_size = os.path.getsize(preview.orig_path)
    
    350 355
             if preview.is_previewable:
    
    351 356
                 if preview.create_thumbnail(data):
    
    ... ... @@ -368,7 +373,7 @@ def _on_thumb_load_finished(data: Optional[bytes],
    368 373
                 return
    
    369 374
     
    
    370 375
             preview.thumbnail = data
    
    371
    -        preview.mime_type = guess_mime_type(preview.orig_path)
    
    376
    +        preview.mime_type = guess_mime_type(preview.orig_path, data)
    
    372 377
             preview.file_size = os.path.getsize(preview.orig_path)
    
    373 378
     
    
    374 379
             try:
    

  • gajim/data/gui/search_view.ui
    1 1
     <?xml version="1.0" encoding="UTF-8"?>
    
    2
    -<!-- Generated with glade 3.38.2 -->
    
    2
    +<!-- Generated with glade 3.40.0 -->
    
    3 3
     <interface>
    
    4 4
       <requires lib="gtk+" version="3.24"/>
    
    5 5
       <object class="GtkPopover" id="calendar_popover">
    
    ... ... @@ -215,6 +215,10 @@
    215 215
             <property name="primary-icon-name">edit-find-symbolic</property>
    
    216 216
             <property name="primary-icon-activatable">False</property>
    
    217 217
             <property name="primary-icon-sensitive">False</property>
    
    218
    +        <property name="primary-icon-tooltip-text" translatable="yes">Use filters to narrow down your search:
    
    219
    +from:username
    
    220
    +before:yyyy-mm-dd
    
    221
    +after:yyyy-mm-dd</property>
    
    218 222
             <signal name="activate" handler="_on_search" swapped="no"/>
    
    219 223
           </object>
    
    220 224
           <packing>
    

  • gajim/data/other/shortcuts.json
    ... ... @@ -37,5 +37,7 @@
    37 37
         "win.switch-workspace-7": ["<Primary>7", "<Primary>KP_7"],
    
    38 38
         "win.switch-workspace-8": ["<Primary>8", "<Primary>KP_8"],
    
    39 39
         "win.switch-workspace-9": ["<Primary>9", "<Primary>KP_9"],
    
    40
    -    "win.toggle-chat-list": ["<Primary>R"]
    
    40
    +    "win.toggle-chat-list": ["<Primary>R"],
    
    41
    +    "win.scroll-view-up": ["<Shift>Page_Up"],
    
    42
    +    "win.scroll-view-down": ["<Shift>Page_Down"]
    
    41 43
     }

  • gajim/gtk/chat_banner.py
    ... ... @@ -17,6 +17,7 @@
    17 17
     from typing import Any
    
    18 18
     from typing import Optional
    
    19 19
     
    
    20
    +from gi.repository import GLib
    
    20 21
     from gi.repository import Gtk
    
    21 22
     
    
    22 23
     import cairo
    
    ... ... @@ -31,7 +32,6 @@
    31 32
     from gajim.common.events import MessageReceived
    
    32 33
     from gajim.common.events import MucDiscoUpdate
    
    33 34
     from gajim.common.ged import EventHelper
    
    34
    -from gajim.common.helpers import get_uf_chatstate
    
    35 35
     from gajim.common.i18n import _
    
    36 36
     from gajim.common.modules.contacts import BareContact
    
    37 37
     from gajim.common.modules.contacts import GroupchatContact
    
    ... ... @@ -63,37 +63,54 @@ def __init__(self) -> None:
    63 63
     
    
    64 64
             hide_roster = app.settings.get('hide_groupchat_occupants_list')
    
    65 65
             self._set_toggle_roster_button_icon(hide_roster)
    
    66
    +
    
    66 67
             app.settings.connect_signal(
    
    67 68
                 'hide_groupchat_occupants_list',
    
    68 69
                 self._set_toggle_roster_button_icon)
    
    69 70
     
    
    70
    -        self.show_all()
    
    71
    +        app.settings.connect_signal(
    
    72
    +            'hide_chat_banner',
    
    73
    +            self._update_hide_banner)
    
    71 74
     
    
    72
    -    def clear(self) -> None:
    
    73
    -        if self._contact is not None:
    
    74
    -            self._contact.disconnect_all_from_obj(self)
    
    75
    -            self._contact = None
    
    75
    +        app.settings.connect_signal(
    
    76
    +            'hide_groupchat_banner',
    
    77
    +            self._update_hide_banner)
    
    76 78
     
    
    77
    -        if self._client is not None:
    
    78
    -            self._client.disconnect_all_from_obj(self)
    
    79
    -            self._client = None
    
    79
    +        self.show_all()
    
    80 80
     
    
    81
    +    def clear(self) -> None:
    
    82
    +        self._disconnect_signals()
    
    81 83
             self.unregister_events()
    
    82 84
     
    
    83 85
         def switch_contact(self, contact: types.ChatContactT) -> None:
    
    84
    -        self._update_account_badge(contact.account)
    
    86
    +        self._disconnect_signals()
    
    87
    +        self._client = app.get_client(contact.account)
    
    88
    +        self._contact = contact
    
    89
    +        self._connect_signals()
    
    85 90
     
    
    86
    -        if self._client is not None:
    
    87
    -            self._client.disconnect_all_from_obj(self)
    
    91
    +        if not self.has_events_registered():
    
    92
    +            self.register_events([
    
    93
    +                ('message-received', ged.GUI2, self._on_message_received),
    
    94
    +                ('muc-disco-update', ged.GUI2, self._on_muc_disco_update),
    
    95
    +                ('bookmarks-received', ged.GUI2, self._on_bookmarks_received),
    
    96
    +                ('account-enabled', ged.GUI2, self._on_account_changed),
    
    97
    +                ('account-disabled', ged.GUI2, self._on_account_changed)
    
    98
    +            ])
    
    88 99
     
    
    89
    -        self._client = app.get_client(contact.account)
    
    90
    -        self._client.connect_signal('state-changed',
    
    91
    -                                    self._on_client_state_changed)
    
    100
    +        self._voice_requests_button.switch_contact(self._contact)
    
    92 101
     
    
    93
    -        if self._contact is not None:
    
    94
    -            self._contact.disconnect_all_from_obj(self)
    
    102
    +        self._update_phone_image()
    
    103
    +        self._update_roster_button()
    
    104
    +        self._update_avatar()
    
    105
    +        self._update_visitor_button()
    
    106
    +        self._update_name_label()
    
    107
    +        self._update_account_badge()
    
    108
    +        self._update_hide_banner()
    
    109
    +
    
    110
    +    def _connect_signals(self) -> None:
    
    111
    +        assert self._contact is not None
    
    112
    +        assert self._client is not None
    
    95 113
     
    
    96
    -        self._contact = contact
    
    97 114
             self._contact.multi_connect({
    
    98 115
                 'chatstate-update': self._on_chatstate_update,
    
    99 116
                 'nickname-update': self._on_nickname_update,
    
    ... ... @@ -108,93 +125,81 @@ def switch_contact(self, contact: types.ChatContactT) -> None:
    108 125
                     'state-changed': self._on_muc_state_changed,
    
    109 126
                     'room-voice-request': self._on_room_voice_request
    
    110 127
                 })
    
    111
    -            self._ui.toggle_roster_button.show()
    
    112
    -            hide_banner = app.settings.get('hide_groupchat_banner')
    
    113
    -        else:
    
    114
    -            self._ui.toggle_roster_button.hide()
    
    115
    -            hide_banner = app.settings.get('hide_chat_banner')
    
    116 128
     
    
    117
    -        if isinstance(self._contact, GroupchatParticipant):
    
    129
    +        elif isinstance(self._contact, GroupchatParticipant):
    
    118 130
                 self._contact.multi_connect({
    
    119 131
                     'user-joined': self._on_user_state_changed,
    
    120 132
                     'user-left': self._on_user_state_changed,
    
    121 133
                     'user-avatar-update': self._on_user_avatar_update,
    
    122 134
                 })
    
    123 135
     
    
    124
    -        if not self.has_events_registered():
    
    125
    -            self.register_events([
    
    126
    -                ('message-received', ged.GUI2, self._on_message_received),
    
    127
    -                ('muc-disco-update', ged.GUI2, self._on_muc_disco_update),
    
    128
    -                ('bookmarks-received', ged.GUI2, self._on_bookmarks_received),
    
    129
    -                ('account-enabled', ged.GUI2, self._on_account_changed),
    
    130
    -                ('account-disabled', ged.GUI2, self._on_account_changed)
    
    131
    -            ])
    
    132
    -
    
    133
    -        self._ui.phone_image.set_visible(
    
    134
    -            self._contact in self._last_message_from_phone)
    
    135
    -
    
    136
    -        self._voice_requests_button.switch_contact(self._contact)
    
    136
    +        self._client.connect_signal('state-changed',
    
    137
    +                                    self._on_client_state_changed)
    
    137 138
     
    
    138
    -        if hide_banner:
    
    139
    -            self.set_no_show_all(True)
    
    140
    -            self.hide()
    
    141
    -        else:
    
    142
    -            self.set_no_show_all(False)
    
    143
    -            self.show_all()
    
    139
    +    def _disconnect_signals(self) -> None:
    
    140
    +        if self._contact is not None:
    
    141
    +            self._contact.disconnect_all_from_obj(self)
    
    142
    +            self._contact = None
    
    144 143
     
    
    145
    -        self._update_avatar()
    
    146
    -        self._update_content()
    
    144
    +        if self._client is not None:
    
    145
    +            self._client.disconnect_all_from_obj(self)
    
    146
    +            self._client = None
    
    147 147
     
    
    148 148
         def _on_client_state_changed(self,
    
    149 149
                                      _client: types.Client,
    
    150 150
                                      _signal_name: str,
    
    151 151
                                      state: SimpleClientState):
    
    152
    +
    
    152 153
             self._update_avatar()
    
    153
    -        self._update_content()
    
    154 154
     
    
    155 155
         def _on_presence_update(self,
    
    156 156
                                 _contact: types.BareContact,
    
    157 157
                                 _signal_name: str
    
    158 158
                                 ) -> None:
    
    159
    +
    
    159 160
             self._update_avatar()
    
    160 161
     
    
    161 162
         def _on_chatstate_update(self,
    
    162 163
                                  _contact: types.BareContact,
    
    163 164
                                  _signal_name: str
    
    164 165
                                  ) -> None:
    
    165
    -        self._update_content()
    
    166
    +
    
    167
    +        self._update_name_label()
    
    166 168
     
    
    167 169
         def _on_nickname_update(self,
    
    168 170
                                 _contact: types.BareContact,
    
    169 171
                                 _signal_name: str
    
    170 172
                                 ) -> None:
    
    171
    -        self._update_content()
    
    173
    +
    
    174
    +        self._update_name_label()
    
    172 175
     
    
    173 176
         def _on_avatar_update(self,
    
    174 177
                               _contact: types.BareContact,
    
    175 178
                               _signal_name: str
    
    176 179
                               ) -> None:
    
    180
    +
    
    177 181
             self._update_avatar()
    
    178 182
     
    
    179 183
         def _on_caps_update(self,
    
    180 184
                             _contact: types.BareContact,
    
    181 185
                             _signal_name: str
    
    182 186
                             ) -> None:
    
    187
    +
    
    183 188
             self._update_avatar()
    
    184 189
     
    
    185 190
         def _on_muc_state_changed(self,
    
    186 191
                                   contact: GroupchatContact,
    
    187 192
                                   _signal_name: str
    
    188 193
                                   ) -> None:
    
    189
    -        if contact.is_joined:
    
    190
    -            self._update_content()
    
    194
    +
    
    195
    +        self._update_visitor_button()
    
    191 196
     
    
    192 197
         def _on_room_voice_request(self, *args: Any) -> None:
    
    193 198
             self._voice_requests_button.set_no_show_all(False)
    
    194 199
             self._voice_requests_button.show_all()
    
    195 200
     
    
    196 201
         def _on_user_role_changed(self, *args: Any) -> None:
    
    197
    -        self._update_content()
    
    202
    +        self._update_visitor_button()
    
    198 203
     
    
    199 204
         def _on_user_state_changed(self, *args: Any) -> None:
    
    200 205
             self._update_avatar()
    
    ... ... @@ -206,18 +211,17 @@ def _on_bookmarks_received(self, _event: BookmarksReceived) -> None:
    206 211
             if not isinstance(self._contact, GroupchatContact):
    
    207 212
                 return
    
    208 213
     
    
    209
    -        self._update_content()
    
    214
    +        self._update_name_label()
    
    210 215
     
    
    211 216
         def _on_muc_disco_update(self, event: MucDiscoUpdate) -> None:
    
    212 217
             assert self._contact is not None
    
    213 218
             if event.jid != self._contact.jid:
    
    214 219
                 return
    
    215 220
     
    
    216
    -        self._update_content()
    
    221
    +        self._update_name_label()
    
    217 222
     
    
    218 223
         def _on_account_changed(self, event: AccountEnabled) -> None:
    
    219
    -        assert self._contact is not None
    
    220
    -        self._update_account_badge(self._contact.account)
    
    224
    +        self._update_account_badge()
    
    221 225
     
    
    222 226
         def _on_message_received(self, event: MessageReceived) -> None:
    
    223 227
             if (not isinstance(self._contact, BareContact) or
    
    ... ... @@ -233,7 +237,31 @@ def _on_message_received(self, event: MessageReceived) -> None:
    233 237
             else:
    
    234 238
                 self._last_message_from_phone.discard(self._contact)
    
    235 239
     
    
    236
    -        self._ui.phone_image.set_visible(resource_contact.is_phone)
    
    240
    +        self._update_phone_image()
    
    241
    +
    
    242
    +    def _update_phone_image(self) -> None:
    
    243
    +        self._ui.phone_image.set_visible(
    
    244
    +            self._contact in self._last_message_from_phone)
    
    245
    +
    
    246
    +    def _update_roster_button(self) -> None:
    
    247
    +        self._ui.toggle_roster_button.set_visible(
    
    248
    +            isinstance(self._contact, GroupchatContact))
    
    249
    +
    
    250
    +    def _update_hide_banner(self, *args: Any) -> None:
    
    251
    +        if self._contact is None:
    
    252
    +            return
    
    253
    +
    
    254
    +        if isinstance(self._contact, GroupchatContact):
    
    255
    +            hide_banner = app.settings.get('hide_groupchat_banner')
    
    256
    +        else:
    
    257
    +            hide_banner = app.settings.get('hide_chat_banner')
    
    258
    +
    
    259
    +        if hide_banner:
    
    260
    +            self.set_no_show_all(True)
    
    261
    +            self.hide()
    
    262
    +        else:
    
    263
    +            self.set_no_show_all(False)
    
    264
    +            self.show_all()
    
    237 265
     
    
    238 266
         def _update_avatar(self) -> None:
    
    239 267
             scale = app.window.get_scale_factor()
    
    ... ... @@ -242,54 +270,50 @@ def _update_avatar(self) -> None:
    242 270
             assert isinstance(surface, cairo.ImageSurface)
    
    243 271
             self._ui.avatar_image.set_from_surface(surface)
    
    244 272
     
    
    245
    -    def _update_content(self) -> None:
    
    246
    -        assert self._contact is not None
    
    247
    -        assert self._client is not None
    
    273
    +    def _update_visitor_button(self) -> None:
    
    274
    +        if not isinstance(self._contact, GroupchatContact):
    
    275
    +            self._ui.visitor_box.set_visible(False)
    
    276
    +            return
    
    248 277
     
    
    249
    -        name = self._contact.name
    
    278
    +        if self._contact.is_not_joined:
    
    279
    +            self._ui.visitor_box.set_visible(False)
    
    280
    +            return
    
    250 281
     
    
    251
    -        if self._contact.jid.bare_match(self._client.get_own_jid()):
    
    252
    -            name = _('Note to myself')
    
    282
    +        self_contact = self._contact.get_self()
    
    283
    +        assert self_contact is not None
    
    284
    +        self._ui.visitor_box.set_visible(self_contact.role.is_visitor)
    
    253 285
     
    
    254
    -        if self._contact.is_pm_contact:
    
    255
    -            gc_contact = self._client.get_module('Contacts').get_contact(
    
    256
    -                self._contact.jid.bare)
    
    257
    -            name = f'{name} ({gc_contact.name})'
    
    286
    +    def _update_name_label(self) -> None:
    
    287
    +        assert self._contact is not None
    
    288
    +
    
    289
    +        name = self._get_name_from_contact(self._contact)
    
    258 290
     
    
    259
    -        label_text = f'<span>{name}</span>'
    
    260
    -        label_tooltip = name
    
    261
    -        show_chatstate = app.settings.get('show_chatstate_in_banner')
    
    262
    -        if (show_chatstate and isinstance(
    
    263
    -                self._contact, (BareContact, GroupchatParticipant))):
    
    264
    -            chatstate = self._contact.chatstate
    
    265
    -            if chatstate is not None:
    
    266
    -                chatstate = get_uf_chatstate(chatstate.value)
    
    267
    -            else:
    
    268
    -                chatstate = ''
    
    291
    +        chatstate = ''
    
    292
    +        if app.settings.get('show_chatstate_in_banner'):
    
    293
    +            chatstate = self._contact.chatstate_string
    
    269 294
     
    
    270
    -            label_text = f'<span>{name}</span>' \
    
    271
    -                         f'<span size="60%" weight="light">' \
    
    272
    -                         f' {chatstate}</span>'
    
    273
    -            label_tooltip = f'{name} {chatstate}'
    
    295
    +        label_text = f'<span>{GLib.markup_escape_text(name)}</span>'
    
    296
    +        if chatstate:
    
    297
    +            label_markup = '<span size="60%" weight="light"> %s</span>'
    
    298
    +            label_text += label_markup % GLib.markup_escape_text(chatstate)
    
    274 299
     
    
    275 300
             self._ui.name_label.set_markup(label_text)
    
    276
    -        self._ui.name_label.set_tooltip_text(label_tooltip)
    
    277 301
     
    
    278
    -        if isinstance(self._contact, GroupchatContact):
    
    279
    -            self_contact = self._contact.get_self()
    
    280
    -            if self_contact:
    
    281
    -                self._ui.visitor_box.set_visible(self_contact.role.is_visitor)
    
    282
    -                return
    
    302
    +        tooltip_text = name
    
    303
    +        if chatstate:
    
    304
    +            tooltip_text = f'{name} {chatstate}'
    
    283 305
     
    
    284
    -        self._ui.visitor_box.set_visible(False)
    
    306
    +        self._ui.name_label.set_tooltip_text(tooltip_text)
    
    307
    +
    
    308
    +    def _update_account_badge(self) -> None:
    
    309
    +        assert self._contact is not None
    
    285 310
     
    
    286
    -    def _update_account_badge(self, account: str) -> None:
    
    287 311
             if self._account_badge is not None:
    
    288 312
                 self._account_badge.destroy()
    
    289 313
     
    
    290 314
             enabled_accounts = app.get_enabled_accounts_with_labels()
    
    291 315
             if len(enabled_accounts) > 1:
    
    292
    -            self._account_badge = AccountBadge(account)
    
    316
    +            self._account_badge = AccountBadge(self._contact.account)
    
    293 317
                 self._ui.additional_items_box.pack_end(
    
    294 318
                     self._account_badge, False, True, 0)
    
    295 319
     
    
    ... ... @@ -308,3 +332,17 @@ def _set_toggle_roster_button_icon(self,
    308 332
             icon = 'go-next-symbolic' if not hide_roster else 'go-previous-symbolic'
    
    309 333
             self._ui.toggle_roster_image.set_from_icon_name(
    
    310 334
                 icon, Gtk.IconSize.BUTTON)
    
    335
    +
    
    336
    +    @staticmethod
    
    337
    +    def _get_name_from_contact(contact: types.ChatContactT) -> str:
    
    338
    +        name = contact.name
    
    339
    +
    
    340
    +        if isinstance(contact, BareContact):
    
    341
    +            if contact.is_self:
    
    342
    +                return _('Note to myself')
    
    343
    +            return name
    
    344
    +
    
    345
    +        if isinstance(contact, GroupchatParticipant):
    
    346
    +            return f'{name} ({contact.room.name})'
    
    347
    +
    
    348
    +        return name

  • gajim/gtk/chat_stack.py
    ... ... @@ -704,9 +704,23 @@ def _on_send_message(self) -> None:
    704 704
     
    
    705 705
             correct_id = None
    
    706 706
             if self._message_action_box.is_correcting:
    
    707
    +            self._message_action_box.is_correcting = False
    
    708
    +            context = self._message_action_box.msg_textview.get_style_context()
    
    709
    +            context.remove_class('gajim-msg-correcting')
    
    710
    +
    
    707 711
                 correct_id = self._message_action_box.last_message_id.get(
    
    708 712
                     (contact.account, contact.jid))
    
    709
    -            self._message_action_box.is_correcting = False
    
    713
    +            message_row = app.storage.archive.get_last_correctable_message(
    
    714
    +                contact.account, contact.jid, correct_id)
    
    715
    +            if message_row is None:
    
    716
    +                self._message_action_box.msg_textview.clear()
    
    717
    +                log.info('Trying to correct message older than threshold')
    
    718
    +                return
    
    719
    +
    
    720
    +            if message_row.message == message:
    
    721
    +                self._message_action_box.msg_textview.clear()
    
    722
    +                log.info('Trying to correct message with original text')
    
    723
    +                return
    
    710 724
     
    
    711 725
             chatstate = client.get_module('Chatstate').get_active_chatstate(
    
    712 726
                 contact)
    

  • gajim/gtk/const.py
    ... ... @@ -214,6 +214,8 @@ def __str__(self):
    214 214
         ('muc-change-role', 'as', False),
    
    215 215
         ('muc-change-affiliation', 'as', False),
    
    216 216
         ('muc-request-voice', None, False),
    
    217
    +    ('scroll-view-up', None, True),
    
    218
    +    ('scroll-view-down', None, True),
    
    217 219
     ]
    
    218 220
     
    
    219 221
     
    

  • gajim/gtk/control.py
    ... ... @@ -48,8 +48,8 @@
    48 48
     from gajim.common.modules.contacts import GroupchatContact
    
    49 49
     from gajim.common.modules.httpupload import HTTPFileTransfer
    
    50 50
     from gajim.common.storage.archive import ConversationRow
    
    51
    +from gajim.gtk.conversation.view import ConversationView
    
    51 52
     
    
    52
    -from gajim.gui.conversation.scrolled import ScrolledView
    
    53 53
     from gajim.gui.conversation.jump_to_end_button import JumpToEndButton
    
    54 54
     from gajim.gui.builder import get_builder
    
    55 55
     from gajim.gui.groupchat_roster import GroupchatRoster
    
    ... ... @@ -69,15 +69,13 @@ def __init__(self) -> None:
    69 69
     
    
    70 70
             self._ui = get_builder('chat_control.ui')
    
    71 71
     
    
    72
    -        self._scrolled_view = ScrolledView()
    
    72
    +        self._scrolled_view = ConversationView()
    
    73 73
             self._scrolled_view.connect('autoscroll-changed',
    
    74 74
                                         self._on_autoscroll_changed)
    
    75 75
             self._scrolled_view.connect('request-history',
    
    76 76
                                         self._fetch_n_lines_history, 20)
    
    77 77
             self._ui.conv_view_overlay.add(self._scrolled_view)
    
    78 78
     
    
    79
    -        self.conversation_view = self._scrolled_view.get_view()
    
    80
    -
    
    81 79
             self._groupchat_state = GroupchatState()
    
    82 80
             self._ui.conv_view_overlay.add_overlay(self._groupchat_state)
    
    83 81
     
    
    ... ... @@ -136,7 +134,6 @@ def clear(self) -> None:
    136 134
     
    
    137 135
             self._contact = None
    
    138 136
             self._client = None
    
    139
    -        self.reset_view()
    
    140 137
             self._scrolled_view.clear()
    
    141 138
             self._groupchat_state.clear()
    
    142 139
             self._roster.clear()
    
    ... ... @@ -199,7 +196,7 @@ def switch_contact(self, contact: Union[BareContact,
    199 196
                 muc_data = self._client.get_module('MUC').get_muc_data(
    
    200 197
                     str(contact.jid))
    
    201 198
                 if muc_data is not None and muc_data.subject is not None:
    
    202
    -                self.conversation_view.add_muc_subject(
    
    199
    +                self._scrolled_view.add_muc_subject(
    
    203 200
                         muc_data.subject, muc_data.last_subject_timestamp)
    
    204 201
     
    
    205 202
         def _register_events(self) -> None:
    
    ... ... @@ -245,9 +242,9 @@ def _on_presence_received(self, event: events.PresenceReceived) -> None:
    245 242
             contact = self.client.get_module('Contacts').get_contact(event.fjid)
    
    246 243
             if isinstance(contact, BareContact):
    
    247 244
                 return
    
    248
    -        self.conversation_view.add_user_status(self.contact.name,
    
    249
    -                                               contact.show.value,
    
    250
    -                                               contact.status)
    
    245
    +        self._scrolled_view.add_user_status(self.contact.name,
    
    246
    +                                            contact.show.value,
    
    247
    +                                            contact.status)
    
    251 248
     
    
    252 249
         def _on_message_sent(self, event: events.MessageSent) -> None:
    
    253 250
             if not self._is_event_processable(event):
    
    ... ... @@ -267,7 +264,7 @@ def _on_message_sent(self, event: events.MessageSent) -> None:
    267 264
                 displaymarking = None
    
    268 265
     
    
    269 266
             if event.correct_id:
    
    270
    -            self.conversation_view.correct_message(
    
    267
    +            self._scrolled_view.correct_message(
    
    271 268
                     event.correct_id, event.message, self.get_our_nick())
    
    272 269
                 return
    
    273 270
     
    
    ... ... @@ -363,7 +360,7 @@ def _on_message_updated(self, event: events.MessageUpdated) -> None:
    363 360
             if not self._is_event_processable(event):
    
    364 361
                 return
    
    365 362
     
    
    366
    -        self.conversation_view.correct_message(
    
    363
    +        self._scrolled_view.correct_message(
    
    367 364
                 event.correct_id, event.msgtxt, event.nickname)
    
    368 365
     
    
    369 366
         def _on_message_moderated(self, event: events.MessageModerated) -> None:
    
    ... ... @@ -374,32 +371,32 @@ def _on_message_moderated(self, event: events.MessageModerated) -> None:
    374 371
                 self.contact.account,
    
    375 372
                 event.moderation.moderator_jid,
    
    376 373
                 event.moderation.reason)
    
    377
    -        self.conversation_view.show_message_retraction(
    
    374
    +        self._scrolled_view.show_message_retraction(
    
    378 375
                 event.moderation.stanza_id, text)
    
    379 376
     
    
    380 377
         def _on_receipt_received(self, event: events.ReceiptReceived) -> None:
    
    381 378
             if not self._is_event_processable(event):
    
    382 379
                 return
    
    383 380
     
    
    384
    -        self.conversation_view.show_receipt(event.receipt_id)
    
    381
    +        self._scrolled_view.show_receipt(event.receipt_id)
    
    385 382
     
    
    386 383
         def _on_displayed_received(self, event: events.DisplayedReceived) -> None:
    
    387 384
             if not self._is_event_processable(event):
    
    388 385
                 return
    
    389 386
     
    
    390
    -        self.conversation_view.set_read_marker(event.marker_id)
    
    387
    +        self._scrolled_view.set_read_marker(event.marker_id)
    
    391 388
     
    
    392 389
         def _on_message_error(self, event: events.MessageError) -> None:
    
    393 390
             if not self._is_event_processable(event):
    
    394 391
                 return
    
    395 392
     
    
    396
    -        self.conversation_view.show_error(event.message_id, event.error)
    
    393
    +        self._scrolled_view.show_error(event.message_id, event.error)
    
    397 394
     
    
    398 395
         def _on_call_stopped(self, event: events.CallStopped) -> None:
    
    399 396
             if not self._is_event_processable(event):
    
    400 397
                 return
    
    401 398
     
    
    402
    -        self.conversation_view.update_call_rows()
    
    399
    +        self._scrolled_view.update_call_rows()
    
    403 400
     
    
    404 401
         def _on_jingle_request_received(self,
    
    405 402
                                         event: events.JingleRequestReceived
    
    ... ... @@ -439,7 +436,7 @@ def mark_as_read(self, send_marker: bool = True) -> None:
    439 436
             self._jump_to_end_button.reset_unread_count()
    
    440 437
     
    
    441 438
         def _on_autoscroll_changed(self,
    
    442
    -                               _widget: ScrolledView,
    
    439
    +                               _widget: ConversationView,
    
    443 440
                                    autoscroll: bool
    
    444 441
                                    ) -> None:
    
    445 442
     
    
    ... ... @@ -480,17 +477,17 @@ def _allow_add_message(self) -> bool:
    480 477
             return self._scrolled_view.get_lower_complete()
    
    481 478
     
    
    482 479
         def add_command_output(self, text: str, is_error: bool) -> None:
    
    483
    -        self.conversation_view.add_command_output(text, is_error)
    
    480
    +        self._scrolled_view.add_command_output(text, is_error)
    
    484 481
     
    
    485 482
         def add_info_message(self,
    
    486 483
                              text: str,
    
    487 484
                              timestamp: Optional[float] = None
    
    488 485
                              ) -> None:
    
    489 486
     
    
    490
    -        self.conversation_view.add_info_message(text, timestamp)
    
    487
    +        self._scrolled_view.add_info_message(text, timestamp)
    
    491 488
     
    
    492 489
         def add_file_transfer(self, transfer: HTTPFileTransfer) -> None:
    
    493
    -        self.conversation_view.add_file_transfer(transfer)
    
    490
    +        self._scrolled_view.add_file_transfer(transfer)
    
    494 491
     
    
    495 492
         def add_jingle_file_transfer(self,
    
    496 493
                                      event: Union[
    
    ... ... @@ -499,11 +496,11 @@ def add_jingle_file_transfer(self,
    499 496
                                          None]
    
    500 497
                                      ) -> None:
    
    501 498
             if self._allow_add_message():
    
    502
    -            self.conversation_view.add_jingle_file_transfer(event)
    
    499
    +            self._scrolled_view.add_jingle_file_transfer(event)
    
    503 500
     
    
    504 501
         def add_call_message(self, event: events.JingleRequestReceived) -> None:
    
    505 502
             if self._allow_add_message():
    
    506
    -            self.conversation_view.add_call_message(event=event)
    
    503
    +            self._scrolled_view.add_call_message(event=event)
    
    507 504
     
    
    508 505
         def _add_message(self,
    
    509 506
                          text: str,
    
    ... ... @@ -521,7 +518,7 @@ def _add_message(self,
    521 518
                 additional_data = AdditionalDataDict()
    
    522 519
     
    
    523 520
             if self._allow_add_message():
    
    524
    -            self.conversation_view.add_message(
    
    521
    +            self._scrolled_view.add_message(
    
    525 522
                     text,
    
    526 523
                     kind,
    
    527 524
                     name,
    
    ... ... @@ -547,7 +544,7 @@ def get_autoscroll(self) -> bool:
    547 544
             return self._scrolled_view.get_autoscroll()
    
    548 545
     
    
    549 546
         def scroll_to_message(self, log_line_id: int, timestamp: float) -> None:
    
    550
    -        row = self.conversation_view.get_row_by_log_line_id(log_line_id)
    
    547
    +        row = self._scrolled_view.get_row_by_log_line_id(log_line_id)
    
    551 548
             if row is None:
    
    552 549
                 # Clear view and reload conversation around timestamp
    
    553 550
                 self._scrolled_view.block_signals(True)
    
    ... ... @@ -558,7 +555,7 @@ def scroll_to_message(self, log_line_id: int, timestamp: float) -> None:
    558 555
                 self.add_messages(at_after)
    
    559 556
     
    
    560 557
             GLib.idle_add(
    
    561
    -            self.conversation_view.scroll_to_message_and_highlight,
    
    558
    +            self._scrolled_view.scroll_to_message_and_highlight,
    
    562 559
                 log_line_id)
    
    563 560
             GLib.idle_add(self._scrolled_view.block_signals, False)
    
    564 561
     
    
    ... ... @@ -571,9 +568,9 @@ def _fetch_n_lines_history(self,
    571 568
             self._scrolled_view.block_signals(True)
    
    572 569
     
    
    573 570
             if before:
    
    574
    -            row = self.conversation_view.get_first_message_row()
    
    571
    +            row = self._scrolled_view.get_first_message_row()
    
    575 572
             else:
    
    576
    -            row = self.conversation_view.get_last_message_row()
    
    573
    +            row = self._scrolled_view.get_last_message_row()
    
    577 574
     
    
    578 575
             if row is None:
    
    579 576
                 timestamp = time.time()
    
    ... ... @@ -598,7 +595,7 @@ def _fetch_n_lines_history(self,
    598 595
                 self._scrolled_view.set_history_complete(before, True)
    
    599 596
     
    
    600 597
             # if self._scrolled_view.get_autoscroll():
    
    601
    -        #    if self.conversation_view.reduce_message_count(before):
    
    598
    +        #    if self._scrolled_view.reduce_message_count(before):
    
    602 599
             #        self._scrolled_view.set_history_complete(before, False)
    
    603 600
     
    
    604 601
             assert self._contact is not None
    
    ... ... @@ -645,13 +642,13 @@ def add_messages(self, messages: list[ConversationRow]):
    645 642
                 if msg.kind in (KindConstant.FILE_TRANSFER_INCOMING,
    
    646 643
                                 KindConstant.FILE_TRANSFER_OUTGOING):
    
    647 644
                     if msg.additional_data.get_value('gajim', 'type') == 'jingle':
    
    648
    -                    self.conversation_view.add_jingle_file_transfer(
    
    645
    +                    self._scrolled_view.add_jingle_file_transfer(
    
    649 646
                             db_message=msg)
    
    650 647
                     continue
    
    651 648
     
    
    652 649
                 if msg.kind in (KindConstant.CALL_INCOMING,
    
    653 650
                                 KindConstant.CALL_OUTGOING):
    
    654
    -                self.conversation_view.add_call_message(db_message=msg)
    
    651
    +                self._scrolled_view.add_call_message(db_message=msg)
    
    655 652
                     continue
    
    656 653
     
    
    657 654
                 if not msg.message:
    
    ... ... @@ -683,7 +680,7 @@ def add_messages(self, messages: list[ConversationRow]):
    683 680
                         message_text = get_retraction_text(
    
    684 681
                             self.contact.account, retracted_by, reason)
    
    685 682
     
    
    686
    -            self.conversation_view.add_message(
    
    683
    +            self._scrolled_view.add_message(
    
    687 684
                     message_text,
    
    688 685
                     kind,
    
    689 686
                     contact_name,
    
    ... ... @@ -1041,7 +1038,7 @@ def _process_muc_user_joined(self, event: events.MUCUserJoined) -> None:
    1041 1038
     
    
    1042 1039
             if not event.is_self:
    
    1043 1040
                 if self.contact.is_joined:
    
    1044
    -                self.conversation_view.add_muc_user_joined(event)
    
    1041
    +                self._scrolled_view.add_muc_user_joined(event)
    
    1045 1042
                 return
    
    1046 1043
     
    
    1047 1044
             status_codes = event.status_codes or []
    
    ... ... @@ -1083,7 +1080,7 @@ def _process_muc_user_left(self, event: events.MUCUserLeft) -> None:
    1083 1080
     
    
    1084 1081
             if StatusCode.REMOVED_ERROR in status_codes:
    
    1085 1082
                 # Handle 333 before 307, some MUCs add both
    
    1086
    -            self.conversation_view.add_muc_user_left(event, error=True)
    
    1083
    +            self._scrolled_view.add_muc_user_left(event, error=True)
    
    1087 1084
                 return
    
    1088 1085
     
    
    1089 1086
             reason = event.reason
    
    ... ... @@ -1118,7 +1115,7 @@ def _process_muc_user_left(self, event: events.MUCUserLeft) -> None:
    1118 1115
                 message = message.format(nick=nick, by=actor, reason=reason)
    
    1119 1116
     
    
    1120 1117
             else:
    
    1121
    -            self.conversation_view.add_muc_user_left(event)
    
    1118
    +            self._scrolled_view.add_muc_user_left(event)
    
    1122 1119
                 return
    
    1123 1120
     
    
    1124 1121
             self.add_info_message(message, event.timestamp)
    
    ... ... @@ -1158,4 +1155,4 @@ def _on_room_subject(self,
    1158 1155
     
    
    1159 1156
             if (app.settings.get('show_subject_on_join') or
    
    1160 1157
                     not contact.is_joining):
    
    1161
    -            self.conversation_view.add_muc_subject(subject)
    1158
    +            self._scrolled_view.add_muc_subject(subject)

  • gajim/gtk/conversation/scrolled.py deleted
    1
    -# This file is part of Gajim.
    
    2
    -#
    
    3
    -# Gajim is free software; you can redistribute it and/or modify
    
    4
    -# it under the terms of the GNU General Public License as published
    
    5
    -# by the Free Software Foundation; version 3 only.
    
    6
    -#
    
    7
    -# Gajim is distributed in the hope that it will be useful,
    
    8
    -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    9
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    10
    -# GNU General Public License for more details.
    
    11
    -#
    
    12
    -# You should have received a copy of the GNU General Public License
    
    13
    -# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    14
    -
    
    15
    -from __future__ import annotations
    
    16
    -
    
    17
    -from typing import Any
    
    18
    -from typing import Optional
    
    19
    -
    
    20
    -from gi.repository import Gtk
    
    21
    -from gi.repository import GObject
    
    22
    -
    
    23
    -from gajim.common.types import ChatContactT
    
    24
    -
    
    25
    -from .view import ConversationView
    
    26
    -
    
    27
    -
    
    28
    -class ScrolledView(Gtk.ScrolledWindow):
    
    29
    -
    
    30
    -    __gsignals__ = {
    
    31
    -        'request-history': (
    
    32
    -            GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION,
    
    33
    -            None,
    
    34
    -            (bool, )
    
    35
    -        ),
    
    36
    -        'autoscroll-changed': (
    
    37
    -            GObject.SignalFlags.RUN_LAST,
    
    38
    -            None,
    
    39
    -            (bool,)
    
    40
    -        )
    
    41
    -    }
    
    42
    -
    
    43
    -    def __init__(self) -> None:
    
    44
    -        Gtk.ScrolledWindow.__init__(self)
    
    45
    -
    
    46
    -        self.set_overlay_scrolling(False)
    
    47
    -        self.get_style_context().add_class('scrolled-no-border')
    
    48
    -        self.get_style_context().add_class('no-scroll-indicator')
    
    49
    -        self.get_style_context().add_class('scrollbar-style')
    
    50
    -        self.set_shadow_type(Gtk.ShadowType.IN)
    
    51
    -        self.set_vexpand(True)
    
    52
    -
    
    53
    -        self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
    
    54
    -
    
    55
    -        # This is a workaround: as soon as a line break occurs in Gtk.TextView
    
    56
    -        # with word-char wrapping enabled, a hyphen character is automatically
    
    57
    -        # inserted before the line break. This triggers the hscrollbar to show,
    
    58
    -        # see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2384
    
    59
    -        # Using set_hscroll_policy(Gtk.Scrollable.Policy.NEVER) would cause bad
    
    60
    -        # performance during resize, and prevent the window from being shrunk
    
    61
    -        # horizontally under certain conditions (applies to GroupchatControl)
    
    62
    -        self.get_hscrollbar().hide()
    
    63
    -
    
    64
    -        self._current_upper: float = 0
    
    65
    -        self._autoscroll: bool = True
    
    66
    -        self._request_history_at_upper: Optional[float] = None
    
    67
    -        self._upper_complete: bool = False
    
    68
    -        self._lower_complete: bool = False
    
    69
    -        self._requesting: Optional[str] = None
    
    70
    -        self._block_signals = False
    
    71
    -
    
    72
    -        vadjustment = self.get_vadjustment()
    
    73
    -        vadjustment.connect('notify::upper', self._on_adj_upper_changed)
    
    74
    -        vadjustment.connect('notify::value', self._on_adj_value_changed)
    
    75
    -
    
    76
    -        self._view = ConversationView()
    
    77
    -        self.add(self._view)
    
    78
    -        self.set_focus_vadjustment(Gtk.Adjustment())
    
    79
    -
    
    80
    -    def clear(self) -> None:
    
    81
    -        self._block_signals = True
    
    82
    -        self._reset()
    
    83
    -        self._view.clear()
    
    84
    -
    
    85
    -    def switch_contact(self, contact: ChatContactT) -> None:
    
    86
    -        self._block_signals = True
    
    87
    -        self._reset()
    
    88
    -        self._view.switch_contact(contact)
    
    89
    -        self._block_signals = False
    
    90
    -        self._emit('request-history', True)
    
    91
    -
    
    92
    -    def get_autoscroll(self) -> bool:
    
    93
    -        return self._autoscroll
    
    94
    -
    
    95
    -    def get_view(self) -> ConversationView:
    
    96
    -        return self._view
    
    97
    -
    
    98
    -    def block_signals(self, value: bool) -> None:
    
    99
    -        self._block_signals = value
    
    100
    -
    
    101
    -    def _emit(self, signal_name: str, *args: Any) -> None:
    
    102
    -        if not self._block_signals:
    
    103
    -            self.emit(signal_name, *args)
    
    104
    -
    
    105
    -    def _reset(self) -> None:
    
    106
    -        self._current_upper = 0
    
    107
    -        self._request_history_at_upper = None
    
    108
    -        self._upper_complete = False
    
    109
    -        self._lower_complete = False
    
    110
    -        self._requesting = None
    
    111
    -        self.set_history_complete(True, False)
    
    112
    -
    
    113
    -    def reset(self) -> None:
    
    114
    -        self._reset()
    
    115
    -        self._view.reset()
    
    116
    -
    
    117
    -    def set_history_complete(self, before: bool, complete: bool) -> None:
    
    118
    -        if before:
    
    119
    -            self._upper_complete = complete
    
    120
    -            self._view.set_history_complete(complete)
    
    121
    -        else:
    
    122
    -            self._lower_complete = complete
    
    123
    -
    
    124
    -    def get_lower_complete(self) -> bool:
    
    125
    -        return self._lower_complete
    
    126
    -
    
    127
    -    def _on_adj_upper_changed(self,
    
    128
    -                              adj: Gtk.Adjustment,
    
    129
    -                              _pspec: GObject.ParamSpec) -> None:
    
    130
    -
    
    131
    -        upper = adj.get_upper()
    
    132
    -        diff = upper - self._current_upper
    
    133
    -
    
    134
    -        if diff != 0:
    
    135
    -            self._current_upper = upper
    
    136
    -            if self._autoscroll:
    
    137
    -                adj.set_value(adj.get_upper() - adj.get_page_size())
    
    138
    -            else:
    
    139
    -                # Workaround
    
    140
    -                # https://gitlab.gnome.org/GNOME/gtk/merge_requests/395
    
    141
    -                self.set_kinetic_scrolling(True)
    
    142
    -                if self._requesting == 'before':
    
    143
    -                    adj.set_value(adj.get_value() + diff)
    
    144
    -
    
    145
    -        if upper == adj.get_page_size():
    
    146
    -            # There is no scrollbar
    
    147
    -            if not self._block_signals:
    
    148
    -                self._emit('request-history', True)
    
    149
    -            self._lower_complete = True
    
    150
    -            self._autoscroll = True
    
    151
    -            self._emit('autoscroll-changed', self._autoscroll)
    
    152
    -
    
    153
    -        self._requesting = None
    
    154
    -
    
    155
    -    def _on_adj_value_changed(self,
    
    156
    -                              adj: Gtk.Adjustment,
    
    157
    -                              _pspec: GObject.ParamSpec) -> None:
    
    158
    -
    
    159
    -        if self._requesting is not None:
    
    160
    -            return
    
    161
    -
    
    162
    -        bottom = adj.get_upper() - adj.get_page_size()
    
    163
    -        if (bottom - adj.get_value()) < 1:
    
    164
    -            self._autoscroll = True
    
    165
    -            self._emit('autoscroll-changed', self._autoscroll)
    
    166
    -        else:
    
    167
    -            self._autoscroll = False
    
    168
    -            self._emit('autoscroll-changed', self._autoscroll)
    
    169
    -
    
    170
    -        if self._upper_complete:
    
    171
    -            self._request_history_at_upper = None
    
    172
    -            if self._lower_complete:
    
    173
    -                return
    
    174
    -
    
    175
    -        if self._request_history_at_upper == adj.get_upper():
    
    176
    -            # Abort here if we already did a history request and the upper
    
    177
    -            # did not change. This can happen if we scroll very fast and the
    
    178
    -            # value changes while the request has not been fulfilled.
    
    179
    -            return
    
    180
    -
    
    181
    -        self._request_history_at_upper = None
    
    182
    -
    
    183
    -        distance = adj.get_page_size() * 2
    
    184
    -        if adj.get_value() < distance:
    
    185
    -            # Load messages when we are near the top
    
    186
    -            if self._upper_complete:
    
    187
    -                return
    
    188
    -            self._request_history_at_upper = adj.get_upper()
    
    189
    -            # Workaround: https://gitlab.gnome.org/GNOME/gtk/merge_requests/395
    
    190
    -            self.set_kinetic_scrolling(False)
    
    191
    -            if not self._block_signals:
    
    192
    -                self._emit('request-history', True)
    
    193
    -            self._requesting = 'before'
    
    194
    -        elif (adj.get_upper() - (adj.get_value() + adj.get_page_size()) <
    
    195
    -                distance):
    
    196
    -            # ..or near the bottom
    
    197
    -            if self._lower_complete:
    
    198
    -                return
    
    199
    -            # Workaround: https://gitlab.gnome.org/GNOME/gtk/merge_requests/395
    
    200
    -            self.set_kinetic_scrolling(False)
    
    201
    -            if not self._block_signals:
    
    202
    -                self._emit('request-history', False)
    
    203
    -            self._requesting = 'after'

  • gajim/gtk/conversation/view.py
    ... ... @@ -15,6 +15,7 @@
    15 15
     from __future__ import annotations
    
    16 16
     
    
    17 17
     from typing import Any
    
    18
    +from typing import Literal
    
    18 19
     from typing import Optional
    
    19 20
     from typing import Union
    
    20 21
     from typing import Generator
    
    ... ... @@ -28,6 +29,8 @@
    28 29
     
    
    29 30
     from gi.repository import GLib
    
    30 31
     from gi.repository import Gtk
    
    32
    +from gi.repository import GObject
    
    33
    +from gi.repository import Gio
    
    31 34
     
    
    32 35
     from nbxmpp.errors import StanzaError
    
    33 36
     from nbxmpp.modules.security_labels import Displaymarking
    
    ... ... @@ -64,13 +67,47 @@
    64 67
     log = logging.getLogger('gajim.gui.conversation_view')
    
    65 68
     
    
    66 69
     
    
    67
    -class ConversationView(Gtk.ListBox):
    
    68
    -    def __init__(self) -> None:
    
    69
    -        Gtk.ListBox.__init__(self)
    
    70
    -        self.set_selection_mode(Gtk.SelectionMode.NONE)
    
    71
    -        self.set_sort_func(self._sort_func)
    
    70
    +class ConversationView(Gtk.ScrolledWindow):
    
    72 71
     
    
    73
    -        self._contact = None
    
    72
    +    __gsignals__ = {
    
    73
    +        'request-history': (
    
    74
    +            GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION,
    
    75
    +            None,
    
    76
    +            (bool, )
    
    77
    +        ),
    
    78
    +        'autoscroll-changed': (
    
    79
    +            GObject.SignalFlags.RUN_LAST,
    
    80
    +            None,
    
    81
    +            (bool,)
    
    82
    +        )
    
    83
    +    }
    
    84
    +
    
    85
    +    def __init__(self) -> None:
    
    86
    +        Gtk.ScrolledWindow.__init__(self)
    
    87
    +
    
    88
    +        self.set_overlay_scrolling(False)
    
    89
    +        self.get_style_context().add_class('scrolled-no-border')
    
    90
    +        self.get_style_context().add_class('no-scroll-indicator')
    
    91
    +        self.get_style_context().add_class('scrollbar-style')
    
    92
    +        self.set_shadow_type(Gtk.ShadowType.IN)
    
    93
    +        self.set_vexpand(True)
    
    94
    +
    
    95
    +        self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
    
    96
    +
    
    97
    +        # This is a workaround: as soon as a line break occurs in Gtk.TextView
    
    98
    +        # with word-char wrapping enabled, a hyphen character is automatically
    
    99
    +        # inserted before the line break. This triggers the hscrollbar to show,
    
    100
    +        # see: https://gitlab.gnome.org/GNOME/gtk/-/issues/2384
    
    101
    +        # Using set_hscroll_policy(Gtk.Scrollable.Policy.NEVER) would cause bad
    
    102
    +        # performance during resize, and prevent the window from being shrunk
    
    103
    +        # horizontally under certain conditions (applies to GroupchatControl)
    
    104
    +        self.get_hscrollbar().hide()
    
    105
    +
    
    106
    +        self._list_box = Gtk.ListBox()
    
    107
    +        self._list_box.set_selection_mode(Gtk.SelectionMode.NONE)
    
    108
    +        self._list_box.set_sort_func(self._sort_func)
    
    109
    +
    
    110
    +        self._contact: Optional[ChatContactT] = None
    
    74 111
             self._client = None
    
    75 112
     
    
    76 113
             # Keeps track of the number of rows shown in ConversationView
    
    ... ... @@ -86,24 +123,70 @@ def __init__(self) -> None:
    86 123
             self._read_marker_row = None
    
    87 124
             self._scroll_hint_row = None
    
    88 125
     
    
    89
    -    @property
    
    90
    -    def contact(self) -> types.ChatContactT:
    
    91
    -        assert self._contact is not None
    
    92
    -        return self._contact
    
    126
    +        self._current_upper: float = 0
    
    127
    +        self._autoscroll: bool = True
    
    128
    +        self._request_history_at_upper: Optional[float] = None
    
    129
    +        self._upper_complete: bool = False
    
    130
    +        self._lower_complete: bool = True
    
    131
    +        self._requesting: Optional[str] = None
    
    132
    +        self._block_signals = False
    
    93 133
     
    
    94
    -    def clear(self) -> None:
    
    95
    -        self._contact = None
    
    96
    -        self._client = None
    
    134
    +        self._signal_handlers_enabled = False
    
    135
    +        self._signal_handler_ids = (0, 0)
    
    136
    +
    
    137
    +        self.add(self._list_box)
    
    138
    +        self.set_focus_vadjustment(Gtk.Adjustment())
    
    139
    +
    
    140
    +        app.window.get_action('scroll-view-up').connect(
    
    141
    +            'activate', self._on_scroll_view)
    
    142
    +        app.window.get_action('scroll-view-down').connect(
    
    143
    +            'activate', self._on_scroll_view)
    
    144
    +
    
    145
    +    def _on_scroll_view(self,
    
    146
    +                        action: Gio.SimpleAction,
    
    147
    +                        _param: Literal[None]) -> None:
    
    148
    +
    
    149
    +        action_name = action.get_name()
    
    150
    +        if action_name == 'scroll-view-down':
    
    151
    +            self.emit('scroll-child', Gtk.ScrollType.PAGE_DOWN, False)
    
    97 152
     
    
    153
    +        elif action_name == 'scroll-view-up':
    
    154
    +            self.emit('scroll-child', Gtk.ScrollType.PAGE_UP, False)
    
    155
    +
    
    156
    +    def _enable_signal_handlers(self, enable: bool) -> None:
    
    157
    +        if self._signal_handlers_enabled == enable:
    
    158
    +            return
    
    159
    +
    
    160
    +        vadjustment = self.get_vadjustment()
    
    161
    +
    
    162
    +        if enable:
    
    163
    +            upper_id = vadjustment.connect('notify::upper',
    
    164
    +                                           self._on_adj_upper_changed)
    
    165
    +            value_id = vadjustment.connect('notify::value',
    
    166
    +                                           self._on_adj_value_changed)
    
    167
    +            self._signal_handler_ids = (upper_id, value_id)
    
    168
    +        else:
    
    169
    +            upper_id, value_id = self._signal_handler_ids
    
    170
    +            vadjustment.disconnect(upper_id)
    
    171
    +            vadjustment.disconnect(value_id)
    
    172
    +
    
    173
    +        self._signal_handlers_enabled = enable
    
    174
    +
    
    175
    +    def clear(self) -> None:
    
    98 176
             app.settings.disconnect_signals(self)
    
    177
    +        self._enable_signal_handlers(False)
    
    178
    +        self._reset()
    
    99 179
     
    
    100
    -        self.reset()
    
    180
    +        self._contact = None
    
    181
    +        self._client = None
    
    101 182
     
    
    102 183
         def switch_contact(self, contact: ChatContactT) -> None:
    
    103 184
             self._contact = contact
    
    104 185
             self._client = app.get_client(contact.account)
    
    105 186
     
    
    106
    -        self.reset()
    
    187
    +        self._enable_signal_handlers(False)
    
    188
    +        self._block_signals = True
    
    189
    +        self._reset()
    
    107 190
     
    
    108 191
             app.settings.disconnect_signals(self)
    
    109 192
     
    
    ... ... @@ -117,11 +200,30 @@ def switch_contact(self, contact: ChatContactT) -> None:
    117 200
                                         account=contact.account,
    
    118 201
                                         jid=contact.jid)
    
    119 202
     
    
    120
    -    def get_row_at_index(self, index: int) -> BaseRow:
    
    121
    -        return cast(BaseRow, Gtk.ListBox.get_row_at_index(self, index))
    
    203
    +        self._block_signals = False
    
    204
    +        self._enable_signal_handlers(True)
    
    205
    +        self._emit('request-history', True)
    
    122 206
     
    
    123
    -    def reset(self) -> None:
    
    124
    -        for row in self.get_children():
    
    207
    +    def get_autoscroll(self) -> bool:
    
    208
    +        return self._autoscroll
    
    209
    +
    
    210
    +    def block_signals(self, value: bool) -> None:
    
    211
    +        self._block_signals = value
    
    212
    +
    
    213
    +    def _emit(self, signal_name: str, *args: Any) -> None:
    
    214
    +        if not self._block_signals:
    
    215
    +            log.debug('emit %s, %s', signal_name, args)
    
    216
    +            self.emit(signal_name, *args)
    
    217
    +
    
    218
    +    def _reset(self) -> None:
    
    219
    +        self._current_upper = 0
    
    220
    +        self._request_history_at_upper = None
    
    221
    +        self._upper_complete = False
    
    222
    +        self._lower_complete = True
    
    223
    +        self._requesting = None
    
    224
    +        self.set_history_complete(True, False)
    
    225
    +
    
    226
    +        for row in self._list_box.get_children():
    
    125 227
                 row.destroy()
    
    126 228
     
    
    127 229
             self._row_count = 0
    
    ... ... @@ -134,30 +236,125 @@ def reset(self) -> None:
    134 236
                 # These need to be present if ConversationView is reset
    
    135 237
                 # without switch_contact being invoked
    
    136 238
                 self._read_marker_row = ReadMarkerRow(self._contact)
    
    137
    -            self.add(self._read_marker_row)
    
    239
    +            self._list_box.add(self._read_marker_row)
    
    138 240
     
    
    139 241
                 self._scroll_hint_row = ScrollHintRow(self._contact.account)
    
    140
    -            self.add(self._scroll_hint_row)
    
    242
    +            self._list_box.add(self._scroll_hint_row)
    
    243
    +
    
    244
    +    def reset(self) -> None:
    
    245
    +        assert self._contact is not None
    
    246
    +        self.switch_contact(self._contact)
    
    247
    +
    
    248
    +    def set_history_complete(self, before: bool, complete: bool) -> None:
    
    249
    +        if before:
    
    250
    +            self._upper_complete = complete
    
    251
    +            if self._scroll_hint_row is None:
    
    252
    +                return
    
    253
    +            self._scroll_hint_row.set_history_complete(complete)
    
    254
    +        else:
    
    255
    +            self._lower_complete = complete
    
    256
    +
    
    257
    +    def get_lower_complete(self) -> bool:
    
    258
    +        return self._lower_complete
    
    259
    +
    
    260
    +    def _on_adj_upper_changed(self,
    
    261
    +                              adj: Gtk.Adjustment,
    
    262
    +                              _pspec: GObject.ParamSpec) -> None:
    
    263
    +
    
    264
    +        upper = adj.get_upper()
    
    265
    +        diff = upper - self._current_upper
    
    266
    +
    
    267
    +        if diff != 0:
    
    268
    +            self._current_upper = upper
    
    269
    +            if self._autoscroll:
    
    270
    +                adj.set_value(adj.get_upper() - adj.get_page_size())
    
    271
    +            else:
    
    272
    +                # Workaround
    
    273
    +                # https://gitlab.gnome.org/GNOME/gtk/merge_requests/395
    
    274
    +                self.set_kinetic_scrolling(True)
    
    275
    +                if self._requesting == 'before':
    
    276
    +                    adj.set_value(adj.get_value() + diff)
    
    277
    +
    
    278
    +        if upper == adj.get_page_size():
    
    279
    +            # There is no scrollbar
    
    280
    +            if not self._block_signals:
    
    281
    +                self._emit('request-history', True)
    
    282
    +            self._lower_complete = True
    
    283
    +            self._autoscroll = True
    
    284
    +            self._emit('autoscroll-changed', self._autoscroll)
    
    285
    +
    
    286
    +        self._requesting = None
    
    287
    +
    
    288
    +    def _on_adj_value_changed(self,
    
    289
    +                              adj: Gtk.Adjustment,
    
    290
    +                              _pspec: GObject.ParamSpec) -> None:
    
    291
    +
    
    292
    +        if self._requesting is not None:
    
    293
    +            return
    
    294
    +
    
    295
    +        bottom = adj.get_upper() - adj.get_page_size()
    
    296
    +
    
    297
    +        self._autoscroll = bottom - adj.get_value() < 1
    
    298
    +        self._emit('autoscroll-changed', self._autoscroll)
    
    299
    +
    
    300
    +        if self._upper_complete:
    
    301
    +            self._request_history_at_upper = None
    
    302
    +            if self._lower_complete:
    
    303
    +                return
    
    304
    +
    
    305
    +        if self._request_history_at_upper == adj.get_upper():
    
    306
    +            # Abort here if we already did a history request and the upper
    
    307
    +            # did not change. This can happen if we scroll very fast and the
    
    308
    +            # value changes while the request has not been fulfilled.
    
    309
    +            return
    
    310
    +
    
    311
    +        self._request_history_at_upper = None
    
    312
    +
    
    313
    +        distance = adj.get_page_size() * 2
    
    314
    +        if adj.get_value() < distance:
    
    315
    +            # Load messages when we are near the top
    
    316
    +            if self._upper_complete:
    
    317
    +                return
    
    318
    +            self._request_history_at_upper = adj.get_upper()
    
    319
    +            # Workaround: https://gitlab.gnome.org/GNOME/gtk/merge_requests/395
    
    320
    +            self.set_kinetic_scrolling(False)
    
    321
    +            if not self._block_signals:
    
    322
    +                self._emit('request-history', True)
    
    323
    +            self._requesting = 'before'
    
    324
    +
    
    325
    +        elif (adj.get_upper() - (adj.get_value() + adj.get_page_size()) <
    
    326
    +                distance):
    
    327
    +            # ..or near the bottom
    
    328
    +            if self._lower_complete:
    
    329
    +                return
    
    330
    +            # Workaround: https://gitlab.gnome.org/GNOME/gtk/merge_requests/395
    
    331
    +            self.set_kinetic_scrolling(False)
    
    332
    +            if not self._block_signals:
    
    333
    +                self._emit('request-history', False)
    
    334
    +            self._requesting = 'after'
    
    335
    +
    
    336
    +    @property
    
    337
    +    def contact(self) -> types.ChatContactT:
    
    338
    +        assert self._contact is not None
    
    339
    +        return self._contact
    
    340
    +
    
    341
    +    def _get_row_at_index(self, index: int) -> BaseRow:
    
    342
    +        return cast(BaseRow, self._list_box.get_row_at_index(index))
    
    141 343
     
    
    142 344
         def get_first_message_row(self) -> Optional[MessageRow]:
    
    143
    -        for row in self.get_children():
    
    345
    +        for row in self._list_box.get_children():
    
    144 346
                 if isinstance(row, MessageRow):
    
    145 347
                     return row
    
    146 348
             return None
    
    147 349
     
    
    148 350
         def get_last_message_row(self) -> Optional[MessageRow]:
    
    149
    -        children = self.get_children()
    
    351
    +        children = self._list_box.get_children()
    
    150 352
             children.reverse()
    
    151 353
             for row in children:
    
    152 354
                 if isinstance(row, MessageRow):
    
    153 355
                     return row
    
    154 356
             return None
    
    155 357
     
    
    156
    -    def set_history_complete(self, complete: bool) -> None:
    
    157
    -        if self._scroll_hint_row is None:
    
    158
    -            return
    
    159
    -        self._scroll_hint_row.set_history_complete(complete)
    
    160
    -
    
    161 358
         @staticmethod
    
    162 359
         def _sort_func(row1: BaseRow, row2: BaseRow) -> int:
    
    163 360
             if row1.timestamp == row2.timestamp:
    
    ... ... @@ -293,7 +490,7 @@ def add_message(self,
    293 490
             self._insert_message(message_row)
    
    294 491
     
    
    295 492
         def _insert_message(self, message: BaseRow) -> None:
    
    296
    -        self.add(message)
    
    493
    +        self._list_box.add(message)
    
    297 494
             self._add_date_row(message.timestamp)
    
    298 495
             self._check_for_merge(message)
    
    299 496
             assert self._read_marker_row is not None
    
    ... ... @@ -311,9 +508,9 @@ def _add_date_row(self, timestamp: datetime) -> None:
    311 508
     
    
    312 509
             date_row = DateRow(self.contact.account, start_of_day)
    
    313 510
             self._active_date_rows.add(start_of_day)
    
    314
    -        self.add(date_row)
    
    511
    +        self._list_box.add(date_row)
    
    315 512
     
    
    316
    -        row = self.get_row_at_index(date_row.get_index() + 1)
    
    513
    +        row = self._get_row_at_index(date_row.get_index() + 1)
    
    317 514
             if row is None:
    
    318 515
                 return
    
    319 516
     
    
    ... ... @@ -340,7 +537,7 @@ def _find_ancestor(self, message: MessageRow) -> Optional[MessageRow]:
    340 537
             index = message.get_index()
    
    341 538
             while index != 0:
    
    342 539
                 index -= 1
    
    343
    -            row = self.get_row_at_index(index)
    
    540
    +            row = self._get_row_at_index(index)
    
    344 541
                 if row is None:
    
    345 542
                     return None
    
    346 543
     
    
    ... ... @@ -361,7 +558,7 @@ def _update_descendants(self, message: MessageRow) -> None:
    361 558
             index = message.get_index()
    
    362 559
             while True:
    
    363 560
                 index += 1
    
    364
    -            row = self.get_row_at_index(index)
    
    561
    +            row = self._get_row_at_index(index)
    
    365 562
                 if row is None:
    
    366 563
                     return
    
    367 564
     
    
    ... ... @@ -382,7 +579,7 @@ def _update_descendants(self, message: MessageRow) -> None:
    382 579
     
    
    383 580
         def reduce_message_count(self, before: bool) -> bool:
    
    384 581
             success = False
    
    385
    -        row_count = len(self.get_children())
    
    582
    +        row_count = len(self._list_box.get_children())
    
    386 583
             while row_count > self._max_row_count:
    
    387 584
                 if before:
    
    388 585
                     if self._reduce_messages_before():
    
    ... ... @@ -399,8 +596,8 @@ def _reduce_messages_before(self) -> bool:
    399 596
             success = False
    
    400 597
     
    
    401 598
             # We want to keep relevant DateRows when removing rows
    
    402
    -        row1 = self.get_row_at_index(2)
    
    403
    -        row2 = self.get_row_at_index(3)
    
    599
    +        row1 = self._get_row_at_index(2)
    
    600
    +        row2 = self._get_row_at_index(3)
    
    404 601
     
    
    405 602
             if row1.type == 'date' and row2.type == 'date':
    
    406 603
                 # First two rows are date rows,
    
    ... ... @@ -422,12 +619,12 @@ def _reduce_messages_before(self) -> bool:
    422 619
             return success
    
    423 620
     
    
    424 621
         def _reduce_messages_after(self) -> None:
    
    425
    -        row = self.get_row_at_index(len(self.get_children()) - 1)
    
    622
    +        row = self._get_row_at_index(len(self._list_box.get_children()) - 1)
    
    426 623
             row.destroy()
    
    427 624
     
    
    428 625
         def scroll_to_message_and_highlight(self, log_line_id: int) -> None:
    
    429 626
             highlight_row = None
    
    430
    -        for row in cast(list[BaseRow], self.get_children()):
    
    627
    +        for row in cast(list[BaseRow], self._list_box.get_children()):
    
    431 628
                 row.get_style_context().remove_class(
    
    432 629
                     'conversation-search-highlight')
    
    433 630
                 if row.log_line_id == log_line_id:
    
    ... ... @@ -443,7 +640,7 @@ def _get_row_by_message_id(self, id_: str) -> Optional[MessageRow]:
    443 640
             return self._message_id_row_map.get(id_)
    
    444 641
     
    
    445 642
         def get_row_by_log_line_id(self, log_line_id: int) -> Optional[MessageRow]:
    
    446
    -        for row in cast(list[BaseRow], self.get_children()):
    
    643
    +        for row in cast(list[BaseRow], self._list_box.get_children()):
    
    447 644
                 if not isinstance(row, MessageRow):
    
    448 645
                     continue
    
    449 646
                 if row.log_line_id == log_line_id:
    
    ... ... @@ -451,7 +648,7 @@ def get_row_by_log_line_id(self, log_line_id: int) -> Optional[MessageRow]:
    451 648
             return None
    
    452 649
     
    
    453 650
         def get_row_by_stanza_id(self, stanza_id: str) -> Optional[MessageRow]:
    
    454
    -        for row in cast(list[BaseRow], self.get_children()):
    
    651
    +        for row in cast(list[BaseRow], self._list_box.get_children()):
    
    455 652
                 if not isinstance(row, MessageRow):
    
    456 653
                     continue
    
    457 654
                 if row.stanza_id == stanza_id:
    
    ... ... @@ -459,7 +656,7 @@ def get_row_by_stanza_id(self, stanza_id: str) -> Optional[MessageRow]:
    459 656
             return None
    
    460 657
     
    
    461 658
         def iter_rows(self) -> Generator[BaseRow, None, None]:
    
    462
    -        for row in cast(list[BaseRow], self.get_children()):
    
    659
    +        for row in cast(list[BaseRow], self._list_box.get_children()):
    
    463 660
                 yield row
    
    464 661
     
    
    465 662
         def remove_rows_by_type(self, row_type: str) -> None:
    
    ... ... @@ -468,7 +665,7 @@ def remove_rows_by_type(self, row_type: str) -> None:
    468 665
                     row.destroy()
    
    469 666
     
    
    470 667
         def update_call_rows(self) -> None:
    
    471
    -        for row in cast(list[BaseRow], self.get_children()):
    
    668
    +        for row in cast(list[BaseRow], self._list_box.get_children()):
    
    472 669
                 if isinstance(row, CallRow):
    
    473 670
                     row.update()
    
    474 671
     
    
    ... ... @@ -489,7 +686,7 @@ def set_read_marker(self, id_: str) -> None:
    489 686
             self._read_marker_row.set_timestamp(timestamp)
    
    490 687
     
    
    491 688
         def update_avatars(self) -> None:
    
    492
    -        for row in cast(list[BaseRow], self.get_children()):
    
    689
    +        for row in cast(list[BaseRow], self._list_box.get_children()):
    
    493 690
                 if isinstance(row, MessageRow):
    
    494 691
                     row.update_avatar()
    
    495 692
     
    
    ... ... @@ -538,5 +735,5 @@ def _on_contact_setting_changed(self,
    538 735
                 self.remove_rows_by_type('muc-user-status')
    
    539 736
     
    
    540 737
         def remove(self, widget: Gtk.Widget) -> None:
    
    541
    -        super().remove(widget)
    
    738
    +        self._list_box.remove(widget)
    
    542 739
             widget.destroy()

  • gajim/gtk/message_actions_box.py
    ... ... @@ -515,12 +515,6 @@ def _on_msg_textview_key_press_event(self,
    515 515
                             Direction.PREV, unread_first=True)
    
    516 516
                         return True
    
    517 517
     
    
    518
    -            if event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up):
    
    519
    -                control = app.window.get_control()
    
    520
    -                if control.has_active_chat():
    
    521
    -                    control.conversation_view.event(event)
    
    522
    -                    return True
    
    523
    -
    
    524 518
             if event_state & Gdk.ModifierType.CONTROL_MASK:
    
    525 519
                 if event.keyval == Gdk.KEY_Tab:
    
    526 520
                     app.window.select_next_chat(Direction.NEXT, unread_first=True)
    
    ... ... @@ -659,6 +653,3 @@ def _on_message_sent(self, event: events.MessageSent) -> None:
    659 653
                     self.last_message_id[(account, jid)] = None
    
    660 654
                 else:
    
    661 655
                     self.last_message_id[(account, jid)] = event.message_id
    662
    -
    
    663
    -        self.msg_textview.get_style_context().remove_class(
    
    664
    -            'gajim-msg-correcting')