Daniel Brötzmann pushed to branch newcontrol at gajim / gajim
Commits:
-
bcb64411
by wurstsalat at 2022-07-08T18:24:17+02:00
5 changed files:
- gajim/gtk/chat_action_processor.py
- gajim/gtk/groupchat_nick_completion.py
- gajim/gtk/message_actions_box.py
- gajim/gtk/message_input.py
- test/no_gui/test_nick_completion.py
Changes:
| ... | ... | @@ -38,11 +38,7 @@ |
| 38 | 38 | |
| 39 | 39 | |
| 40 | 40 | class ChatActionProcessor(Gtk.Popover):
|
| 41 | - def __init__(self,
|
|
| 42 | - account: str,
|
|
| 43 | - contact: types.ChatContactT,
|
|
| 44 | - message_input: MessageInputTextView
|
|
| 45 | - ) -> None:
|
|
| 41 | + def __init__(self, message_input: MessageInputTextView) -> None:
|
|
| 46 | 42 | Gtk.Popover.__init__(self)
|
| 47 | 43 | self._menu = Gio.Menu()
|
| 48 | 44 | self.bind_model(self._menu)
|
| ... | ... | @@ -53,23 +49,28 @@ def __init__(self, |
| 53 | 49 | self.connect('closed', self._on_popover_closed)
|
| 54 | 50 | self.connect('destroy', self._on_destroy)
|
| 55 | 51 | |
| 52 | + self._account: Optional[str] = None
|
|
| 53 | + self._contact: Optional[types.ChatContactT] = None
|
|
| 54 | + |
|
| 56 | 55 | self._message_input = message_input
|
| 57 | 56 | self._message_input.connect('key-press-event', self._on_key_press)
|
| 58 | 57 | |
| 59 | 58 | self._buf = message_input.get_buffer()
|
| 60 | 59 | self._buf.connect('changed', self._on_changed)
|
| 61 | 60 | |
| 62 | - self._nick_completion: Optional[GroupChatNickCompletion] = None
|
|
| 63 | - if contact.is_groupchat:
|
|
| 64 | - assert isinstance(contact, GroupchatContact)
|
|
| 65 | - self._nick_completion = GroupChatNickCompletion(
|
|
| 66 | - account, contact, message_input)
|
|
| 61 | + self._nick_completion = GroupChatNickCompletion()
|
|
| 67 | 62 | |
| 68 | 63 | self._start_mark: Optional[Gtk.TextMark] = None
|
| 69 | 64 | self._current_iter: Optional[Gtk.TextIter] = None
|
| 70 | 65 | |
| 71 | 66 | self._active = False
|
| 72 | 67 | |
| 68 | + def switch_contact(self, contact: types.ChatContactT) -> None:
|
|
| 69 | + self._account = contact.account
|
|
| 70 | + self._contact = contact
|
|
| 71 | + if isinstance(contact, GroupchatContact):
|
|
| 72 | + self._nick_completion.switch_contact(contact)
|
|
| 73 | + |
|
| 73 | 74 | def _on_destroy(self, _popover: Gtk.Popover) -> None:
|
| 74 | 75 | app.check_finalize(self)
|
| 75 | 76 | |
| ... | ... | @@ -77,7 +78,7 @@ def _on_key_press(self, |
| 77 | 78 | textview: MessageInputTextView,
|
| 78 | 79 | event: Gdk.EventKey
|
| 79 | 80 | ) -> bool:
|
| 80 | - if self._nick_completion is not None:
|
|
| 81 | + if isinstance(self._contact, GroupchatContact):
|
|
| 81 | 82 | res = self._nick_completion.process_key_press(textview, event)
|
| 82 | 83 | if res:
|
| 83 | 84 | return True
|
| ... | ... | @@ -122,8 +123,9 @@ def _replace_text(self, selected_action: str) -> None: |
| 122 | 123 | |
| 123 | 124 | def _get_commands(self) -> list[str]:
|
| 124 | 125 | commands: list[str] = []
|
| 125 | - control = app.window.get_control(
|
|
| 126 | - self._message_input.account, self._message_input.contact.jid)
|
|
| 126 | + assert self._account
|
|
| 127 | + assert self._contact
|
|
| 128 | + control = app.window.get_control(self._account, self._contact.jid)
|
|
| 127 | 129 | assert control is not None
|
| 128 | 130 | for command in control.list_commands():
|
| 129 | 131 | for name in command.names:
|
| ... | ... | @@ -325,6 +327,9 @@ def _item_has_focus(item: Gtk.ModelButton) -> bool: |
| 325 | 327 | flags = item.get_state_flags()
|
| 326 | 328 | return 'GTK_STATE_FLAG_FOCUSED' in str(flags)
|
| 327 | 329 | |
| 328 | - def process_outgoing_message(self, contact: str, highlight: bool) -> None:
|
|
| 329 | - if self._nick_completion is not None:
|
|
| 330 | - self._nick_completion.record_message(contact, highlight) |
|
| 330 | + def process_outgoing_message(self,
|
|
| 331 | + contact_name: str,
|
|
| 332 | + highlight: bool
|
|
| 333 | + ) -> None:
|
|
| 334 | + if isinstance(self._contact, GroupchatContact):
|
|
| 335 | + self._nick_completion.process_message(contact_name, highlight) |
| ... | ... | @@ -14,6 +14,7 @@ |
| 14 | 14 | |
| 15 | 15 | from __future__ import annotations
|
| 16 | 16 | |
| 17 | +from typing import Optional
|
|
| 17 | 18 | from typing import TYPE_CHECKING
|
| 18 | 19 | |
| 19 | 20 | import logging
|
| ... | ... | @@ -33,22 +34,38 @@ |
| 33 | 34 | |
| 34 | 35 | |
| 35 | 36 | class GroupChatNickCompletion:
|
| 36 | - def __init__(self,
|
|
| 37 | - account: str,
|
|
| 38 | - contact: types.GroupchatContactT,
|
|
| 39 | - message_input: MessageInputTextView
|
|
| 40 | - ) -> None:
|
|
| 41 | - self._account = account
|
|
| 42 | - |
|
| 43 | - self._contact = contact
|
|
| 44 | - self._contact.connect(
|
|
| 45 | - 'user-nickname-changed', self._on_user_nickname_changed)
|
|
| 37 | + def __init__(self) -> None:
|
|
| 38 | + self._account: Optional[str] = None
|
|
| 39 | + self._contact: Optional[types.GroupchatContactT] = None
|
|
| 46 | 40 | |
| 47 | 41 | self._sender_list: list[str] = []
|
| 48 | - self._attention_list: list[str] = []
|
|
| 42 | + self._highlight_list: list[str] = []
|
|
| 49 | 43 | self._nick_hits: list[str] = []
|
| 50 | 44 | self._last_key_tab = False
|
| 51 | 45 | |
| 46 | + self._nick_data: dict[str, tuple[list[str], list[str]]] = {}
|
|
| 47 | + |
|
| 48 | + def switch_contact(self, contact: types.GroupchatContactT) -> None:
|
|
| 49 | + self._nick_hits.clear()
|
|
| 50 | + self._last_key_tab = False
|
|
| 51 | + |
|
| 52 | + if self._contact is not None:
|
|
| 53 | + self._contact.disconnect_all_from_obj(self)
|
|
| 54 | + self._nick_data[str(self._contact.jid)] = (
|
|
| 55 | + self._sender_list, self._highlight_list)
|
|
| 56 | + |
|
| 57 | + nick_data = self._nick_data.get(str(contact.jid))
|
|
| 58 | + if nick_data is None:
|
|
| 59 | + self._sender_list.clear()
|
|
| 60 | + self._highlight_list.clear()
|
|
| 61 | + else:
|
|
| 62 | + self._sender_list, self._highlight_list = nick_data
|
|
| 63 | + |
|
| 64 | + self._account = contact.account
|
|
| 65 | + self._contact = contact
|
|
| 66 | + self._contact.connect(
|
|
| 67 | + 'user-nickname-changed', self._on_user_nickname_changed)
|
|
| 68 | + |
|
| 52 | 69 | def _on_user_nickname_changed(self,
|
| 53 | 70 | _contact: types.GroupchatContact,
|
| 54 | 71 | _signal_name: str,
|
| ... | ... | @@ -63,12 +80,13 @@ def _on_user_nickname_changed(self, |
| 63 | 80 | new_name = properties.muc_user.nick
|
| 64 | 81 | assert new_name is not None
|
| 65 | 82 | log.debug('Contact %s renamed to %s', old_name, new_name)
|
| 66 | - for lst in (self._attention_list, self._sender_list):
|
|
| 83 | + for lst in (self._highlight_list, self._sender_list):
|
|
| 67 | 84 | for idx, contact in enumerate(lst):
|
| 68 | 85 | if contact == old_name:
|
| 69 | 86 | lst[idx] = new_name
|
| 70 | 87 | |
| 71 | - def record_message(self, contact_name: str, highlight: bool) -> None:
|
|
| 88 | + def process_message(self, contact_name: str, highlight: bool) -> None:
|
|
| 89 | + assert self._contact
|
|
| 72 | 90 | if contact_name == self._contact.nickname:
|
| 73 | 91 | return
|
| 74 | 92 | |
| ... | ... | @@ -78,12 +96,12 @@ def record_message(self, contact_name: str, highlight: bool) -> None: |
| 78 | 96 | |
| 79 | 97 | if highlight:
|
| 80 | 98 | try:
|
| 81 | - self._attention_list.remove(contact_name)
|
|
| 99 | + self._highlight_list.remove(contact_name)
|
|
| 82 | 100 | except ValueError:
|
| 83 | 101 | pass
|
| 84 | - if len(self._attention_list) > 6:
|
|
| 85 | - self._attention_list.pop(0) # remove older
|
|
| 86 | - self._attention_list.append(contact_name)
|
|
| 102 | + if len(self._highlight_list) > 6:
|
|
| 103 | + self._highlight_list.pop(0) # remove older
|
|
| 104 | + self._highlight_list.append(contact_name)
|
|
| 87 | 105 | |
| 88 | 106 | # TODO implement it in a more efficient way
|
| 89 | 107 | # Currently it's O(n*m + n*s), where n is the number of participants and
|
| ... | ... | @@ -111,12 +129,13 @@ def _generate_suggestions(self, |
| 111 | 129 | `beginning` is the text already typed by the user
|
| 112 | 130 | """
|
| 113 | 131 | def _nick_matching(nick: str) -> bool:
|
| 132 | + assert self._contact
|
|
| 114 | 133 | return (nick != self._contact.nickname and
|
| 115 | 134 | nick.lower().startswith(beginning.lower()))
|
| 116 | 135 | |
| 117 | 136 | if beginning == '':
|
| 118 | 137 | # empty message, so just suggest recent mentions
|
| 119 | - potential_matches = self._attention_list
|
|
| 138 | + potential_matches = self._highlight_list
|
|
| 120 | 139 | else:
|
| 121 | 140 | # nick partially typed, try completing it
|
| 122 | 141 | potential_matches = self._sender_list
|
| ... | ... | @@ -179,6 +198,7 @@ def process_key_press(self, |
| 179 | 198 | self._nick_hits.append(self._nick_hits[0])
|
| 180 | 199 | begin = self._nick_hits.pop(0)
|
| 181 | 200 | else:
|
| 201 | + assert self._contact
|
|
| 182 | 202 | list_nick = self._contact.get_user_nicknames()
|
| 183 | 203 | list_nick = list(filter(self._jid_not_blocked, list_nick))
|
| 184 | 204 | |
| ... | ... | @@ -215,6 +235,7 @@ def process_key_press(self, |
| 215 | 235 | else:
|
| 216 | 236 | start_iter.backward_chars(len(begin))
|
| 217 | 237 | |
| 238 | + assert self._account
|
|
| 218 | 239 | client = app.get_client(self._account)
|
| 219 | 240 | client.get_module('Chatstate').block_chatstates(
|
| 220 | 241 | self._contact, True)
|
| ... | ... | @@ -255,6 +276,8 @@ def process_key_press(self, |
| 255 | 276 | return True
|
| 256 | 277 | |
| 257 | 278 | def _jid_not_blocked(self, resource: str) -> bool:
|
| 279 | + assert self._account
|
|
| 280 | + assert self._contact
|
|
| 258 | 281 | resource_contact = self._contact.get_resource(resource)
|
| 259 | 282 | return not jid_is_blocked(
|
| 260 | 283 | self._account, str(resource_contact.jid)) |
| ... | ... | @@ -173,6 +173,7 @@ def switch_contact(self, contact: ChatContactT) -> None: |
| 173 | 173 | self._set_spell_checker_language(contact)
|
| 174 | 174 | self._set_chatstate()
|
| 175 | 175 | |
| 176 | + self.msg_textview.switch_contact(contact)
|
|
| 176 | 177 | self._security_label_selector.switch_contact(contact)
|
| 177 | 178 | |
| 178 | 179 | def _set_chatstate(self) -> None:
|
| ... | ... | @@ -57,9 +57,6 @@ class MessageInputTextView(Gtk.TextView): |
| 57 | 57 | """
|
| 58 | 58 | def __init__(self) -> None:
|
| 59 | 59 | Gtk.TextView.__init__(self)
|
| 60 | - # self.account = account
|
|
| 61 | - # self.contact = contact
|
|
| 62 | - |
|
| 63 | 60 | self.set_border_width(3)
|
| 64 | 61 | self.set_accepts_tab(True)
|
| 65 | 62 | self.set_editable(True)
|
| ... | ... | @@ -76,8 +73,7 @@ def __init__(self) -> None: |
| 76 | 73 | self._undo_list: list[str] = []
|
| 77 | 74 | self.undo_pressed: bool = False
|
| 78 | 75 | |
| 79 | - # self._chat_action_processor = ChatActionProcessor(
|
|
| 80 | - # account, contact, self)
|
|
| 76 | + self._chat_action_processor = ChatActionProcessor(self)
|
|
| 81 | 77 | |
| 82 | 78 | self.get_buffer().create_tag('strong', weight=Pango.Weight.BOLD)
|
| 83 | 79 | self.get_buffer().create_tag('emphasis', style=Pango.Style.ITALIC)
|
| ... | ... | @@ -91,6 +87,9 @@ def __init__(self) -> None: |
| 91 | 87 | self.connect('destroy', self._on_destroy)
|
| 92 | 88 | self.connect('populate-popup', self._on_populate_popup)
|
| 93 | 89 | |
| 90 | + def switch_contact(self, contact: ChatContactT) -> None:
|
|
| 91 | + self._chat_action_processor.switch_contact(contact)
|
|
| 92 | + |
|
| 94 | 93 | def _on_destroy(self, _widget: Gtk.Widget) -> None:
|
| 95 | 94 | # We restore the TextView’s drag destination to avoid a GTK warning
|
| 96 | 95 | # when closing the control. BaseControl.shutdown() calls destroy()
|
| ... | ... | @@ -323,9 +322,8 @@ def process_outgoing_message(self, |
| 323 | 322 | contact_name: str,
|
| 324 | 323 | highlight: bool
|
| 325 | 324 | ) -> None:
|
| 326 | - # self._chat_action_processor.process_outgoing_message(
|
|
| 327 | - # contact_name, highlight)
|
|
| 328 | - pass
|
|
| 325 | + self._chat_action_processor.process_outgoing_message(
|
|
| 326 | + contact_name, highlight)
|
|
| 329 | 327 | |
| 330 | 328 | def _on_populate_popup(self,
|
| 331 | 329 | _textview: MessageInputTextView,
|
| ... | ... | @@ -10,13 +10,12 @@ |
| 10 | 10 | class Test(unittest.TestCase):
|
| 11 | 11 | |
| 12 | 12 | def test_generate_suggestions(self):
|
| 13 | - contact = MagicMock()
|
|
| 14 | 13 | message_input = MagicMock()
|
| 15 | - gen = GroupChatNickCompletion('testacc', contact, message_input)
|
|
| 14 | + gen = GroupChatNickCompletion(message_input)
|
|
| 16 | 15 | |
| 17 | 16 | l = ['aaaa', 'fooo', 'xxxxz', 'xaaaz']
|
| 18 | 17 | for n in l:
|
| 19 | - gen.record_message(n, False)
|
|
| 18 | + gen.process_message(n, False)
|
|
| 20 | 19 | l2 = ['xxx'] + l
|
| 21 | 20 | r = gen._generate_suggestions(nicks=l2, beginning='x')
|
| 22 | 21 | self.assertEqual(r, ['xaaaz', 'xxxxz', 'xxx'])
|
| ... | ... | @@ -28,7 +27,7 @@ def test_generate_suggestions(self): |
| 28 | 27 | self.assertEqual(r, [])
|
| 29 | 28 | |
| 30 | 29 | for n in ['xaaaz', 'xxxxz']:
|
| 31 | - gen.record_message(n, True)
|
|
| 30 | + gen.process_message(n, True)
|
|
| 32 | 31 | |
| 33 | 32 | r = gen._generate_suggestions(
|
| 34 | 33 | nicks=l2,
|