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

Commits:

7 changed files:

Changes:

  • gajim/gtk/chat_stack.py
    ... ... @@ -537,6 +537,7 @@ def _update_group_chat_actions(self, contact: GroupchatContact) -> None:
    537 537
     
    
    538 538
             app.window.get_action('quote').set_enabled(joined)
    
    539 539
             app.window.get_action('mention').set_enabled(joined)
    
    540
    +        app.window.get_action('retract-message').set_enabled(joined)
    
    540 541
     
    
    541 542
         def _update_participant_actions(self,
    
    542 543
                                         contact: GroupchatParticipant) -> None:
    

  • gajim/gtk/const.py
    ... ... @@ -194,6 +194,8 @@ def __str__(self):
    194 194
         ('show-emoji-chooser', None, True),
    
    195 195
         ('activate-message-selection', 'u', True),
    
    196 196
         ('correct-message', None, False),
    
    197
    +    ('copy-message', 's', True),
    
    198
    +    ('retract-message', 'a{sv}', False),
    
    197 199
         ('quote', 's', False),
    
    198 200
         ('mention', 's', False),
    
    199 201
         ('send-file-httpupload', None, False),
    

  • gajim/gtk/conversation/rows/message.py
    ... ... @@ -21,7 +21,6 @@
    21 21
     from datetime import timedelta
    
    22 22
     import textwrap
    
    23 23
     
    
    24
    -from gi.repository import Gdk
    
    25 24
     from gi.repository import GdkPixbuf
    
    26 25
     from gi.repository import GLib
    
    27 26
     from gi.repository import Gtk
    
    ... ... @@ -38,7 +37,6 @@
    38 37
     from gajim.common.const import Trust
    
    39 38
     from gajim.common.const import TRUST_SYMBOL_DATA
    
    40 39
     from gajim.common.helpers import AdditionalDataDict
    
    41
    -from gajim.common.helpers import from_one_line
    
    42 40
     from gajim.common.helpers import get_group_chat_nick
    
    43 41
     from gajim.common.helpers import message_needs_highlight
    
    44 42
     from gajim.common.helpers import to_user_string
    
    ... ... @@ -55,8 +53,6 @@
    55 53
     from .widgets import MessageIcons
    
    56 54
     from .widgets import MoreMenuButton
    
    57 55
     from ..message_widget import MessageWidget
    
    58
    -from ...dialogs import InputDialog
    
    59
    -from ...dialogs import DialogButton
    
    60 56
     from ...preview import PreviewWidget
    
    61 57
     from ...util import format_fingerprint
    
    62 58
     
    
    ... ... @@ -314,37 +310,6 @@ def is_mergeable(self, message: MessageRow) -> bool:
    314 310
         def get_text(self) -> str:
    
    315 311
             return self._message_widget.get_text()
    
    316 312
     
    
    317
    -    def on_copy_message(self, _widget: Gtk.Widget) -> None:
    
    318
    -        time_format = from_one_line(app.settings.get('chat_timestamp_format'))
    
    319
    -        timestamp_formatted = self.timestamp.strftime(time_format)
    
    320
    -
    
    321
    -        clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
    
    322
    -        text = self.get_text()
    
    323
    -        clip.set_text(f'{timestamp_formatted} - {self.name}: {text}', -1)
    
    324
    -
    
    325
    -    def on_quote_message(self, _widget: Gtk.Widget) -> None:
    
    326
    -        app.window.activate_action(
    
    327
    -            'quote', GLib.Variant('s', self.get_text()))
    
    328
    -
    
    329
    -    def on_correct_message(self, _widget: Gtk.Widget) -> None:
    
    330
    -        app.window.activate_action('correct-message', None)
    
    331
    -
    
    332
    -    def on_retract_message(self, _widget: Gtk.Widget) -> None:
    
    333
    -        def _on_retract(reason: str) -> None:
    
    334
    -            self._client.get_module('MUC').retract_message(
    
    335
    -                self._contact.jid, self.stanza_id, reason or None)
    
    336
    -
    
    337
    -        InputDialog(
    
    338
    -            _('Retract Message'),
    
    339
    -            _('Retract message?'),
    
    340
    -            _('Why do you want to retract this message?'),
    
    341
    -            [DialogButton.make('Cancel'),
    
    342
    -             DialogButton.make('Remove',
    
    343
    -                               text=_('_Retract'),
    
    344
    -                               callback=_on_retract)],
    
    345
    -            input_str=_('Spam'),
    
    346
    -            transient_for=app.window).show()
    
    347
    -
    
    348 313
         def _get_encryption_image(self,
    
    349 314
                                   additional_data: AdditionalDataDict,
    
    350 315
                                   ) -> Optional[Gtk.Image]:
    

  • gajim/gtk/conversation/rows/widgets.py
    ... ... @@ -28,13 +28,13 @@
    28 28
     from gajim.common import app
    
    29 29
     from gajim.common.const import AvatarSize
    
    30 30
     from gajim.common.i18n import Q_
    
    31
    -from gajim.common.helpers import is_retraction_allowed
    
    32 31
     from gajim.common.modules.contacts import GroupchatContact
    
    33 32
     from gajim.common.types import ChatContactT
    
    34 33
     
    
    35 34
     if TYPE_CHECKING:
    
    36 35
         from .message import MessageRow
    
    37 36
     
    
    37
    +from ...menus import get_chat_row_menu
    
    38 38
     from ...menus import get_groupchat_participant_menu
    
    39 39
     from ...util import wrap_with_event_box
    
    40 40
     from ...util import get_cursor
    
    ... ... @@ -76,112 +76,18 @@ def __init__(self,
    76 76
             self.connect('clicked', self._on_click)
    
    77 77
     
    
    78 78
         def _on_click(self, _button: Gtk.Button) -> None:
    
    79
    -        show_retract = False
    
    80
    -        if isinstance(self._contact, GroupchatContact):
    
    81
    -            if not self._contact.is_joined:
    
    82
    -                self._create_popover()
    
    83
    -                return
    
    84
    -
    
    85
    -            contact = self._contact.get_resource(self._name)
    
    86
    -            self_contact = self._contact.get_self()
    
    87
    -            assert self_contact is not None
    
    88
    -            is_allowed = is_retraction_allowed(self_contact, contact)
    
    89
    -
    
    90
    -            disco_info = app.storage.cache.get_last_disco_info(
    
    91
    -                self._contact.jid)
    
    92
    -            assert disco_info is not None
    
    93
    -
    
    94
    -            if disco_info.has_message_moderation and is_allowed:
    
    95
    -                show_retract = True
    
    96
    -
    
    97
    -        show_correction = False
    
    98
    -        if self._row.message_id is not None:
    
    99
    -            show_correction = app.window.is_message_correctable(
    
    100
    -                self._contact, self._row.message_id)
    
    101
    -
    
    102
    -        self._create_popover(show_retract,
    
    103
    -                             show_correction)
    
    104
    -
    
    105
    -    def _create_popover(self,
    
    106
    -                        show_retract: bool = False,
    
    107
    -                        show_correction: bool = False
    
    108
    -                        ) -> None:
    
    109
    -
    
    110
    -        menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
    
    111
    -        menu_box.get_style_context().add_class('padding-6')
    
    112
    -
    
    113
    -        quote_enabled = True
    
    114
    -        if isinstance(self._contact, GroupchatContact):
    
    115
    -            if self._contact.is_joined:
    
    116
    -                self_contact = self._contact.get_self()
    
    117
    -                assert self_contact is not None
    
    118
    -                quote_enabled = not self_contact.role.is_visitor
    
    119
    -            else:
    
    120
    -                quote_enabled = False
    
    121
    -
    
    122
    -        quote_button = Gtk.ModelButton()
    
    123
    -        quote_button.set_halign(Gtk.Align.START)
    
    124
    -        quote_button.connect('clicked', self._row.on_quote_message)
    
    125
    -        quote_button.set_label(Q_('?Message row action:Quote…'))
    
    126
    -        quote_button.set_image(Gtk.Image.new_from_icon_name(
    
    127
    -            'mail-reply-sender-symbolic', Gtk.IconSize.MENU))
    
    128
    -        quote_button.set_sensitive(quote_enabled)
    
    129
    -        menu_box.add(quote_button)
    
    130
    -
    
    131
    -        copy_button = Gtk.ModelButton()
    
    132
    -        copy_button.set_halign(Gtk.Align.START)
    
    133
    -        copy_button.connect('clicked', self._row.on_copy_message)
    
    134
    -        copy_button.set_label(Q_('?Message row action:Copy'))
    
    135
    -        copy_button.set_image(Gtk.Image.new_from_icon_name(
    
    136
    -            'edit-copy-symbolic', Gtk.IconSize.MENU))
    
    137
    -        menu_box.add(copy_button)
    
    138
    -
    
    139
    -        select_button = Gtk.ModelButton()
    
    140
    -        select_button.set_halign(Gtk.Align.START)
    
    141
    -        select_button.connect('clicked', self._on_activate_message_selection)
    
    142
    -        select_button.set_label(Q_('?Message row action:Select Messages…'))
    
    143
    -        select_button.set_image(Gtk.Image.new_from_icon_name(
    
    144
    -            'edit-select-symbolic', Gtk.IconSize.MENU))
    
    145
    -        menu_box.add(select_button)
    
    146
    -
    
    147
    -        if show_correction:
    
    148
    -            correct_button = Gtk.ModelButton()
    
    149
    -            correct_button.set_halign(Gtk.Align.START)
    
    150
    -            correct_button.connect(
    
    151
    -                'clicked', self._row.on_correct_message)
    
    152
    -            correct_button.set_label(Q_('?Message row action:Correct…'))
    
    153
    -            correct_button.set_image(Gtk.Image.new_from_icon_name(
    
    154
    -                'document-edit-symbolic', Gtk.IconSize.MENU))
    
    155
    -            menu_box.add(correct_button)
    
    156
    -
    
    157
    -        if show_retract:
    
    158
    -            retract_button = Gtk.ModelButton()
    
    159
    -            retract_button.set_halign(Gtk.Align.START)
    
    160
    -            retract_button.connect(
    
    161
    -                'clicked', self._row.on_retract_message)
    
    162
    -            retract_button.set_label(Q_('?Message row action:Retract…'))
    
    163
    -            retract_button.set_image(Gtk.Image.new_from_icon_name(
    
    164
    -                'edit-undo-symbolic', Gtk.IconSize.MENU))
    
    165
    -            menu_box.add(retract_button)
    
    166
    -
    
    167
    -        menu_box.show_all()
    
    168
    -
    
    169
    -        popover = Gtk.PopoverMenu()
    
    170
    -        popover.add(menu_box)
    
    171
    -        popover.set_relative_to(self)
    
    172
    -        popover.set_position(Gtk.PositionType.BOTTOM)
    
    173
    -        popover.connect('closed', self._on_closed)
    
    79
    +        menu = get_chat_row_menu(
    
    80
    +            self._contact,
    
    81
    +            self._name,
    
    82
    +            self._row.get_text(),
    
    83
    +            self._row.timestamp,
    
    84
    +            self._row.message_id,
    
    85
    +            self._row.stanza_id,
    
    86
    +            self._row.log_line_id)
    
    87
    +
    
    88
    +        popover = GajimPopover(menu, relative_to=self)
    
    174 89
             popover.popup()
    
    175 90
     
    
    176
    -    def _on_activate_message_selection(self, _button: Gtk.ModelButton) -> None:
    
    177
    -        app.window.activate_action(
    
    178
    -            'activate-message-selection',
    
    179
    -            GLib.Variant('u', self._row.log_line_id or 0))
    
    180
    -
    
    181
    -    @staticmethod
    
    182
    -    def _on_closed(popover: Gtk.Popover) -> None:
    
    183
    -        GLib.idle_add(popover.destroy)
    
    184
    -
    
    185 91
     
    
    186 92
     class DateTimeLabel(Gtk.Label):
    
    187 93
         def __init__(self, timestamp: datetime) -> None:
    

  • gajim/gtk/main.py
    ... ... @@ -54,6 +54,7 @@
    54 54
     from .dialogs import DialogButton
    
    55 55
     from .dialogs import ConfirmationDialog
    
    56 56
     from .dialogs import ConfirmationCheckDialog
    
    57
    +from .dialogs import InputDialog
    
    57 58
     from .builder import get_builder
    
    58 59
     from .util import get_app_window
    
    59 60
     from .util import resize_window
    
    ... ... @@ -65,6 +66,7 @@
    65 66
     from .structs import AddChatActionParams
    
    66 67
     from .structs import actionmethod
    
    67 68
     from .structs import ChatListEntryParam
    
    69
    +from .structs import RetractMessageParam
    
    68 70
     
    
    69 71
     if TYPE_CHECKING:
    
    70 72
         from .control import ChatControl
    
    ... ... @@ -401,6 +403,8 @@ def _connect_actions(self) -> None:
    401 403
                 ('switch-workspace-8', self._on_action),
    
    402 404
                 ('switch-workspace-9', self._on_action),
    
    403 405
                 ('toggle-chat-list', self._on_action),
    
    406
    +            ('copy-message', self._on_copy_message),
    
    407
    +            ('retract-message', self._on_retract_message),
    
    404 408
                 ('add-workspace', self._add_workspace),
    
    405 409
                 ('edit-workspace', self._edit_workspace),
    
    406 410
                 ('remove-workspace', self._remove_workspace),
    
    ... ... @@ -510,6 +514,36 @@ def _toggle_chat_list(self) -> None:
    510 514
                         'go-previous-symbolic', Gtk.IconSize.BUTTON)
    
    511 515
             self._chat_page.toggle_chat_list()
    
    512 516
     
    
    517
    +    def _on_copy_message(self,
    
    518
    +                         _action: Gio.SimpleAction,
    
    519
    +                         param: GLib.Variant
    
    520
    +                         ) -> None:
    
    521
    +
    
    522
    +        clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
    
    523
    +        clip.set_text(param.get_string(), -1)
    
    524
    +
    
    525
    +    @actionmethod
    
    526
    +    def _on_retract_message(self,
    
    527
    +                            _action: Gio.SimpleAction,
    
    528
    +                            params: RetractMessageParam
    
    529
    +                            ) -> None:
    
    530
    +
    
    531
    +        def _on_retract(reason: str) -> None:
    
    532
    +            client = app.get_client(params.account)
    
    533
    +            client.get_module('MUC').retract_message(
    
    534
    +                params.jid, params.stanza_id, reason or None)
    
    535
    +
    
    536
    +        InputDialog(
    
    537
    +            _('Retract Message'),
    
    538
    +            _('Retract message?'),
    
    539
    +            _('Why do you want to retract this message?'),
    
    540
    +            [DialogButton.make('Cancel'),
    
    541
    +             DialogButton.make('Remove',
    
    542
    +                               text=_('_Retract'),
    
    543
    +                               callback=_on_retract)],
    
    544
    +            input_str=_('Spam'),
    
    545
    +            transient_for=app.window).show()
    
    546
    +
    
    513 547
         def _on_window_motion_notify(self,
    
    514 548
                                      _widget: Gtk.ApplicationWindow,
    
    515 549
                                      _event: Gdk.EventMotion
    

  • gajim/gtk/menus.py
    ... ... @@ -21,6 +21,7 @@
    21 21
     from typing import Optional
    
    22 22
     from typing import Union
    
    23 23
     
    
    24
    +from datetime import datetime
    
    24 25
     import textwrap
    
    25 26
     from urllib.parse import quote
    
    26 27
     
    
    ... ... @@ -32,10 +33,13 @@
    32 33
     
    
    33 34
     from gajim.common import app
    
    34 35
     from gajim.common import types
    
    36
    +from gajim.common.helpers import from_one_line
    
    35 37
     from gajim.common.helpers import is_affiliation_change_allowed
    
    38
    +from gajim.common.helpers import is_retraction_allowed
    
    36 39
     from gajim.common.helpers import is_role_change_allowed
    
    37 40
     from gajim.common.helpers import jid_is_blocked
    
    38 41
     from gajim.common.i18n import _
    
    42
    +from gajim.common.i18n import Q_
    
    39 43
     from gajim.common.i18n import get_short_lang_code
    
    40 44
     from gajim.common.const import URIType
    
    41 45
     from gajim.common.const import XmppUriQuery
    
    ... ... @@ -48,6 +52,7 @@
    48 52
     from gajim.gui.structs import AccountJidParam
    
    49 53
     from gajim.gui.structs import ChatListEntryParam
    
    50 54
     from gajim.gui.structs import RemoveHistoryActionParams
    
    55
    +from gajim.gui.structs import RetractMessageParam
    
    51 56
     from gajim.gui.util import GajimMenu
    
    52 57
     
    
    53 58
     
    
    ... ... @@ -594,6 +599,77 @@ def get_component_search_menu(jid: Optional[str], copy_text: str) -> Gio.Menu:
    594 599
         return menu
    
    595 600
     
    
    596 601
     
    
    602
    +def get_chat_row_menu(contact: types.ChatContactT,
    
    603
    +                      name: str,
    
    604
    +                      text: str,
    
    605
    +                      timestamp: datetime,
    
    606
    +                      message_id: Optional[str],
    
    607
    +                      stanza_id: Optional[str],
    
    608
    +                      log_line_id: Optional[int]
    
    609
    +                      ) -> GajimMenu:
    
    610
    +
    
    611
    +    menu_items: MenuItemListT = []
    
    612
    +
    
    613
    +    text = text.replace('"', '\\"')
    
    614
    +
    
    615
    +    time_format = from_one_line(app.settings.get('chat_timestamp_format'))
    
    616
    +    timestamp_formatted = timestamp.strftime(time_format)
    
    617
    +    copy_text = f'{timestamp_formatted} - {name}: {text}'
    
    618
    +    menu_items.append(
    
    619
    +        (Q_('?Message row action:Copy'), 'win.copy-message', f'"{copy_text}"'))
    
    620
    +
    
    621
    +    menu_items.append(
    
    622
    +        (Q_('?Message row action:Select Messages…'),
    
    623
    +         'win.activate-message-selection',
    
    624
    +         GLib.Variant('u', log_line_id or 0)))
    
    625
    +
    
    626
    +    show_quote = True
    
    627
    +    if isinstance(contact, GroupchatContact):
    
    628
    +        if contact.is_joined:
    
    629
    +            self_contact = contact.get_self()
    
    630
    +            assert self_contact is not None
    
    631
    +            show_quote = not self_contact.role.is_visitor
    
    632
    +        else:
    
    633
    +            show_quote = False
    
    634
    +    if show_quote:
    
    635
    +        menu_items.append((
    
    636
    +            Q_('?Message row action:Quote…'),
    
    637
    +            'win.quote',
    
    638
    +            f'"{text}"'))
    
    639
    +
    
    640
    +    show_correction = False
    
    641
    +    if message_id is not None:
    
    642
    +        show_correction = app.window.is_message_correctable(
    
    643
    +            contact, message_id)
    
    644
    +    if show_correction:
    
    645
    +        menu_items.append((
    
    646
    +            Q_('?Message row action:Correct…'), 'win.correct-message', None))
    
    647
    +
    
    648
    +    show_retract = False
    
    649
    +    if isinstance(contact, GroupchatContact) and contact.is_joined:
    
    650
    +        resource_contact = contact.get_resource(name)
    
    651
    +        self_contact = contact.get_self()
    
    652
    +        assert self_contact is not None
    
    653
    +        is_allowed = is_retraction_allowed(self_contact, resource_contact)
    
    654
    +
    
    655
    +        disco_info = app.storage.cache.get_last_disco_info(contact.jid)
    
    656
    +        assert disco_info is not None
    
    657
    +
    
    658
    +        if disco_info.has_message_moderation and is_allowed:
    
    659
    +            show_retract = True
    
    660
    +    if show_retract:
    
    661
    +        assert stanza_id is not None
    
    662
    +        param = RetractMessageParam(
    
    663
    +            account=contact.account,
    
    664
    +            jid=contact.jid,
    
    665
    +            stanza_id=stanza_id)
    
    666
    +        menu_items.append((
    
    667
    +            Q_('?Message row action:Retract…'), 'win.retract-message', param))
    
    668
    +
    
    669
    +    menu = GajimMenu.from_list(menu_items)
    
    670
    +    return menu
    
    671
    +
    
    672
    +
    
    597 673
     def get_format_menu() -> GajimMenu:
    
    598 674
     
    
    599 675
         menuitems: MenuItemListT = [
    

  • gajim/gtk/structs.py
    ... ... @@ -21,7 +21,9 @@
    21 21
     import inspect
    
    22 22
     import functools
    
    23 23
     from dataclasses import dataclass
    
    24
    -from gi.repository import GLib, Gio
    
    24
    +
    
    25
    +from gi.repository import Gio
    
    26
    +from gi.repository import GLib
    
    25 27
     
    
    26 28
     from nbxmpp.protocol import JID
    
    27 29
     
    
    ... ... @@ -66,6 +68,13 @@ class AccountJidParam(VariantMixin):
    66 68
         jid: JID
    
    67 69
     
    
    68 70
     
    
    71
    +@dataclass
    
    72
    +class RetractMessageParam(VariantMixin):
    
    73
    +    account: str
    
    74
    +    jid: JID
    
    75
    +    stanza_id: str
    
    76
    +
    
    77
    +
    
    69 78
     def get_params_class(func: Callable[..., Any]) -> Any:
    
    70 79
         module = sys.modules[__name__]
    
    71 80
         params = inspect.signature(func).parameters