Daniel Brötzmann pushed to branch newcontrol at gajim / gajim
Commits: e45b19c0 by wurstsalat at 2022-07-20T21:51:03+02:00 ConversationView: Fix linting error
- - - - - 1e44ef2f by wurstsalat at 2022-07-21T20:29:53+02:00 Fix message correction
- - - - -
11 changed files:
- gajim/common/storage/archive.py - gajim/gtk/chat_stack.py - gajim/gtk/const.py - gajim/gtk/controls/base.py - gajim/gtk/controls/chat.py - gajim/gtk/controls/groupchat.py - gajim/gtk/conversation/rows/message.py - gajim/gtk/conversation/rows/widgets.py - gajim/gtk/conversation/view.py - gajim/gtk/main.py - gajim/gtk/message_actions_box.py
Changes:
===================================== gajim/common/storage/archive.py ===================================== @@ -992,6 +992,35 @@ def find_stanza_id(self, return True return False
+ @timeit + def get_last_correctable_message(self, + account: str, + jid: JID, + message_id: str + ) -> Optional[LastConversationRow]: + """ + Load the last correctable message of a conversation by message_id. + Conditions: max 5 min old + """ + jids = [jid] + account_id = self.get_account_id(account) + min_time = time.time() - 5 * 60 + print(min_time) + + sql = ''' + SELECT contact_name, time, kind, message, stanza_id, message_id, + additional_data + FROM logs NATURAL JOIN jids WHERE jid IN ({jids}) + AND account_id = {account_id} + AND message_id = ? + AND time > ? + '''.format(jids=', '.join('?' * len(jids)), + account_id=account_id) + + return self._con.execute( + sql, + tuple(jids) + (message_id, min_time)).fetchone() + @timeit def store_message_correction(self, account: str,
===================================== gajim/gtk/chat_stack.py ===================================== @@ -306,6 +306,8 @@ def _update_base_actions(self, contact: ChatContactT) -> None: httpupload.get_enabled())
app.window.get_action('show-contact-info').set_enabled(online) + app.window.get_action('correct-message').set_enabled(online) + app.window.get_action('input-bold').set_enabled(True) app.window.get_action('input-italic').set_enabled(True) app.window.get_action('input-strike').set_enabled(True) @@ -538,11 +540,11 @@ def _on_send_message(self) -> None:
label = self._message_action_box.get_seclabel()
- correct_id: Optional[str] = None - if control.correcting and control.last_sent_msg: - correct_id = control.last_sent_msg - else: - correct_id = None + correct_id = None + if self._message_action_box.is_correcting: + correct_id = self._message_action_box.last_message_id.get( + (contact.account, contact.jid)) + self._message_action_box.is_correcting = False
chatstate = client.get_module('Chatstate').get_active_chatstate( contact) @@ -563,8 +565,9 @@ def _on_send_message(self) -> None: client.send_message(message_)
self._message_action_box.msg_textview.clear() - self._message_action_box.msg_textview.get_style_context().remove_class( - 'gajim-msg-correcting') + + def get_last_message_id(self, account: str, jid: JID) -> Optional[str]: + return self._message_action_box.last_message_id.get((account, jid))
def _close_control(self) -> None: assert self._current_contact is not None @@ -582,6 +585,7 @@ def process_event(self, event: Any) -> None: if isinstance(event, MucDiscoUpdate): self._on_muc_disco_update(event) self._control_stack.process_event(event) + self._message_action_box.process_event(event)
class ChatPlaceholderBox(Gtk.Box):
===================================== gajim/gtk/const.py ===================================== @@ -194,6 +194,7 @@ def __str__(self): ('input-strike', None, False), ('input-clear', None, False), ('show-emoji-chooser', None, False), + ('correct-message', None, False), ('quote', 's', False), ('mention', 's', False), ('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: # XEP-0333 Chat Markers self.last_msg_id: Optional[str] = None
- # XEP-0308 Message Correction - self.last_sent_msg: Optional[str] = None - # XEP-0172 User Nickname # TODO: self.user_nick: Optional[str] = None
- self.sendmessage: bool = True - self._client.get_module('Chatstate').set_active(self.contact)
self.encryption: Optional[str] = self.get_encryption_state() @@ -501,11 +496,8 @@ def add_message(self, self.last_msg_id = message_id
if kind == 'incoming': - # Record the history of received messages - # self.save_message(text, 'received') - - # Issue notification if notify: + # Issue notification self._notify(name, text, tim, additional_data)
if not chat_active and notify:
===================================== gajim/gtk/controls/chat.py ===================================== @@ -70,12 +70,6 @@ def __init__(self, account: str, jid: JID) -> None: 'chat_control', account, jid) - - self.sendmessage: bool = True - - # XEP-0308 Message Correction - self.last_sent_msg: Optional[str] = None - # PluginSystem: adding GUI extension point for this ChatControl # instance object app.plugin_manager.gui_extension_point('chat_control', self) @@ -132,13 +126,6 @@ def _on_message_sent(self, event: events.MessageSent) -> None: if not event.message: return
- if event.correct_id is None: - oob_url = event.additional_data.get_value('gajim', 'oob_url') - if oob_url == event.message: - self.last_sent_msg = None - else: - self.last_sent_msg = event.message_id - message_id = event.message_id
if event.label:
===================================== gajim/gtk/controls/groupchat.py ===================================== @@ -82,15 +82,6 @@ def __init__(self, account: str, jid: JID) -> None:
self.room_jid = str(self.contact.jid)
- # Last sent message text - self.last_sent_txt: str = '' - - # Attribute, encryption plugins use to signal the message can be sent - self.sendmessage: bool = False - - # XEP-0308 Message Correction - self.last_sent_msg: Optional[str] = None - self._groupchat_state = GroupchatState(self.contact) self.xml.conv_view_overlay.add_overlay(self._groupchat_state)
@@ -226,9 +217,6 @@ def _on_gc_message_received(self, event: events.GcMessageReceived) -> None: displaymarking=event.displaymarking, additional_data=event.additional_data) else: - if event.properties.muc_nickname == self.contact.nickname: - self.last_sent_txt = event.msgtxt - self.add_message(event.msgtxt, contact=event.properties.muc_nickname, tim=event.properties.timestamp, @@ -634,16 +622,7 @@ def _on_room_destroyed(self, self.add_info_message(join_message)
def _on_message_sent(self, event: events.MessageSent) -> None: - if not event.message: - return - # we'll save sent message text when we'll receive it in - # _nec_gc_message_received - if event.correct_id is None: - oob_url = event.additional_data.get_value('gajim', 'oob_url') - if oob_url == event.message: - self.last_sent_msg = None - else: - self.last_sent_msg = event.message_id + pass
def _on_message_error(self, event: events.MessageError) -> None: 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: app.window.activate_action( 'quote', GLib.Variant('s', self._message_widget.get_text()))
+ def on_correct_message(self, _widget: Gtk.Widget) -> None: + app.window.activate_action('correct-message', None) + def on_retract_message(self, _widget: Gtk.Widget) -> None: def _on_retract(reason: str) -> None: self._client.get_module('MUC').retract_message(
===================================== gajim/gtk/conversation/rows/widgets.py ===================================== @@ -14,8 +14,7 @@
from __future__ import annotations
-from typing import Union -from typing import Any +from typing import TYPE_CHECKING
from gi.repository import Gtk from gi.repository import Pango @@ -24,13 +23,13 @@ from gajim.common import app from gajim.common.i18n import _ from gajim.common.helpers import is_retraction_allowed -from gajim.common.modules.contacts import BareContact from gajim.common.modules.contacts import GroupchatContact -from gajim.common.modules.contacts import GroupchatParticipant +from gajim.common.types import ChatContactT
-from ...util import wrap_with_event_box +if TYPE_CHECKING: + from .message import MessageRow
-ContactT = Union[BareContact, GroupchatContact, GroupchatParticipant] +from ...util import wrap_with_event_box
class SimpleLabel(Gtk.Label): @@ -45,8 +44,8 @@ def __init__(self) -> None: @wrap_with_event_box class MoreMenuButton(Gtk.Button): def __init__(self, - row: Any, - contact: ContactT, + row: MessageRow, + contact: ChatContactT, name: str ) -> None:
@@ -70,23 +69,33 @@ def _on_click(self, _button: Gtk.Button) -> None: show_retract = False if isinstance(self._contact, GroupchatContact): if not self._contact.is_joined: - self._create_popover(False) + self._create_popover(show_retract=False) return
- disco_info = app.storage.cache.get_last_disco_info( - self._contact.jid) - assert disco_info is not None - contact = self._contact.get_resource(self._name) self_contact = self._contact.get_self() assert self_contact is not None - is_allowed = is_retraction_allowed(self_contact, contact) + + disco_info = app.storage.cache.get_last_disco_info( + self._contact.jid) + assert disco_info is not None + if disco_info.has_message_moderation and is_allowed: show_retract = True - self._create_popover(show_retract)
- def _create_popover(self, show_retract: bool) -> None: + show_correction = False + if self._row.message_id is not None: + show_correction = app.window.is_message_correctable( + self._contact.account, self._contact.jid, self._row.message_id) + + self._create_popover(show_retract=show_retract, + show_correction=show_correction) + + def _create_popover(self, + show_retract: bool = False, + show_correction: bool = False + ) -> None: menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) menu_box.get_style_context().add_class('padding-6')
@@ -116,6 +125,16 @@ def _create_popover(self, show_retract: bool) -> None: 'edit-copy-symbolic', Gtk.IconSize.MENU)) menu_box.add(copy_button)
+ if show_correction: + correct_button = Gtk.ModelButton() + correct_button.set_halign(Gtk.Align.START) + correct_button.connect( + 'clicked', self._row.on_correct_message) + correct_button.set_label(_('Correct')) + correct_button.set_image(Gtk.Image.new_from_icon_name( + 'document-edit-symbolic', Gtk.IconSize.MENU)) + menu_box.add(correct_button) + if show_retract: retract_button = Gtk.ModelButton() retract_button.set_halign(Gtk.Align.START)
===================================== gajim/gtk/conversation/view.py ===================================== @@ -380,7 +380,7 @@ def _reduce_messages_before(self) -> bool: row1 = self.get_row_at_index(2) row2 = self.get_row_at_index(3)
- if row1.type == row2.type == 'date': + if row1.type == 'date' and row2.type == 'date': # First two rows are date rows, # it’s safe to remove the fist row row1.destroy()
===================================== gajim/gtk/main.py ===================================== @@ -38,7 +38,8 @@ from gajim.common.ged import EventHelper from gajim.common.i18n import _ from gajim.common.modules.bytestream import is_transfer_active -from gajim.gtk.const import MAIN_WIN_ACTIONS, MAIN_WIN_STATEFUL_ACTIONS +from gajim.gtk.const import MAIN_WIN_ACTIONS +from gajim.gtk.const import MAIN_WIN_STATEFUL_ACTIONS from gajim.plugins.pluginmanager import PluginManifest from gajim.plugins.repository import PluginRepository
@@ -731,6 +732,20 @@ def is_chat_loaded(self, account: str, jid: JID) -> bool: def chat_exists(self, account: str, jid: JID) -> bool: return self._chat_page.chat_exists(account, jid)
+ def is_message_correctable(self, + account: str, + jid: JID, + message_id: str + ) -> bool: + chat_stack = self._chat_page.get_chat_stack() + last_message_id = chat_stack.get_last_message_id(account, jid) + if last_message_id is None or last_message_id != message_id: + return False + + message_row = app.storage.archive.get_last_correctable_message( + account, jid, last_message_id) + return message_row is not None + def get_total_unread_count(self) -> int: chat_list_stack = self._chat_page.get_chat_list_stack() return chat_list_stack.get_total_unread_count()
===================================== gajim/gtk/message_actions_box.py ===================================== @@ -32,8 +32,10 @@
from nbxmpp.const import Chatstate from nbxmpp.modules.security_labels import SecurityLabel +from nbxmpp.protocol import JID
from gajim.common import app +from gajim.common import events from gajim.common import i18n from gajim.common.i18n import _ from gajim.common.client import Client @@ -65,7 +67,7 @@
class MessageActionsBox(Gtk.Grid): def __init__(self) -> None: - super().__init__() + Gtk.Grid.__init__(self)
self._client: Optional[Client] = None self._contact: Optional[ChatContactT] = None @@ -75,8 +77,13 @@ def __init__(self) -> None:
self.attach(self._ui.box, 0, 0, 1, 1)
+ # For undo self.space_pressed = False
+ # XEP-0308 Message Correction + self.is_correcting = False + self.last_message_id: dict[tuple[str, JID], Optional[str]] = {} + self._ui.send_message_button.set_visible( app.settings.get('show_send_message_button')) app.settings.bind_signal('show_send_message_button', @@ -90,7 +97,7 @@ def __init__(self) -> None: self.msg_textview.get_buffer().connect('changed', self._on_buffer_changed) self.msg_textview.connect('key-press-event', - self._on_textview_key_press_event) + self._on_msg_textview_key_press_event) self.msg_textview.connect('paste-clipboard', self._on_paste_clipboard)
@@ -133,6 +140,7 @@ def _connect_actions(self) -> None: 'show-emoji-chooser', 'quote', 'mention', + 'correct-message', ]
for action in actions: @@ -179,6 +187,9 @@ def _on_action(self, assert param self.msg_textview.mention_participant(param.get_string())
+ elif action_name == 'correct-message': + self.toggle_message_correction() + def switch_contact(self, contact: ChatContactT) -> None: if self._client is not None: self._set_chatstate(False) @@ -503,10 +514,10 @@ def _on_buffer_changed(self, textbuffer: Gtk.TextBuffer) -> None: client.get_module('Chatstate').set_chatstate( self._contact, Chatstate.COMPOSING)
- def _on_textview_key_press_event(self, - textview: MessageInputTextView, - event: Gdk.EventKey - ) -> bool: + def _on_msg_textview_key_press_event(self, + textview: MessageInputTextView, + event: Gdk.EventKey + ) -> bool: # pylint: disable=too-many-nested-blocks if event.keyval == Gdk.KEY_space: self.space_pressed = True @@ -525,24 +536,33 @@ def _on_textview_key_press_event(self, True)) self.space_pressed = False
+ event_state = event.get_state() # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here - if event.get_state() & Gdk.ModifierType.SHIFT_MASK: - if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and - event.keyval == Gdk.KEY_ISO_Left_Tab): - app.window.select_next_chat(Direction.PREV, unread_first=True) - return True + if event_state & Gdk.ModifierType.SHIFT_MASK: + if event_state & Gdk.ModifierType.CONTROL_MASK: + if event.keyval == Gdk.KEY_ISO_Left_Tab: + app.window.select_next_chat( + Direction.PREV, unread_first=True) + return True
if event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up): self.conversation_view.event(event) return True
- if event.get_state() & Gdk.ModifierType.CONTROL_MASK: + if event_state & Gdk.ModifierType.CONTROL_MASK: if event.keyval == Gdk.KEY_Tab: app.window.select_next_chat(Direction.NEXT, unread_first=True) return True
- event_state = event.get_state() - if event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter): # ENTER + if event.keyval == Gdk.KEY_z: + self.msg_textview.undo() + return True + + if event.keyval == Gdk.KEY_Up: + self.toggle_message_correction() + return True + + if event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter): if event_state & Gdk.ModifierType.SHIFT_MASK: textview.insert_newline() return True @@ -566,11 +586,6 @@ def _on_textview_key_press_event(self, app.window.activate_action('send-message', None) return True
- if event.keyval == Gdk.KEY_z: # CTRL+z - if event_state & Gdk.ModifierType.CONTROL_MASK: - self.msg_textview.undo() - return True - return False
def _on_paste_clipboard(self, @@ -616,3 +631,52 @@ def _paste_event_confirmed(self,
assert self._contact is not None app.interface.start_file_transfer(self._contact, path) + + def toggle_message_correction(self) -> None: + if self.is_correcting: + self.is_correcting = False + self.msg_textview.clear() + self.msg_textview.get_style_context().remove_class( + 'gajim-msg-correcting') + self.msg_textview.grab_focus() + return + + assert self._contact is not None + last_message_id = self.last_message_id.get( + (self._contact.account, self._contact.jid)) + if last_message_id is None: + return + + message_row = app.storage.archive.get_last_correctable_message( + self._contact.account, self._contact.jid, last_message_id) + if message_row is None: + return + + self.is_correcting = True + self.msg_textview.clear() + self.msg_textview.insert_text(message_row.message) + self.msg_textview.get_style_context().add_class('gajim-msg-correcting') + self.msg_textview.grab_focus() + + def _on_message_sent(self, event: events.MessageSent) -> None: + if not event.message: + return + + if event.correct_id is None: + # This wasn't a corrected message + assert self._contact is not None + oob_url = event.additional_data.get_value('gajim', 'oob_url') + account = self._contact.account + jid = self._contact.jid + if oob_url == event.message: + # Don't allow to correct HTTP Upload file transfer URLs + self.last_message_id[(account, jid)] = None + else: + self.last_message_id[(account, jid)] = event.message_id + + self.msg_textview.get_style_context().remove_class( + 'gajim-msg-correcting') + + def process_event(self, event: events.ApplicationEvent) -> None: + if isinstance(event, events.MessageSent): + self._on_message_sent(event)
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/033080b26d7d426f2e607550ae9e7626...