| ... |
... |
@@ -12,18 +12,26 @@ |
|
12
|
12
|
# You should have received a copy of the GNU General Public License
|
|
13
|
13
|
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
14
|
14
|
|
|
|
15
|
+from __future__ import annotations
|
|
|
16
|
+from typing import Any
|
|
|
17
|
+from typing import cast
|
|
15
|
18
|
from typing import Optional
|
|
|
19
|
+from typing import Tuple
|
|
16
|
20
|
|
|
17
|
21
|
import locale
|
|
18
|
22
|
from enum import IntEnum
|
|
19
|
23
|
|
|
|
24
|
+from gi.repository import Gdk
|
|
20
|
25
|
from gi.repository import Gtk
|
|
21
|
26
|
from gi.repository import GLib
|
|
22
|
27
|
from gi.repository import GObject
|
|
23
|
28
|
from nbxmpp.const import Affiliation
|
|
|
29
|
+from nbxmpp.const import Role
|
|
|
30
|
+from nbxmpp.structs import MessageProperties
|
|
24
|
31
|
|
|
25
|
32
|
from gajim.common import app
|
|
26
|
33
|
from gajim.common import ged
|
|
|
34
|
+from gajim.common import types
|
|
27
|
35
|
from gajim.common.helpers import get_uf_role
|
|
28
|
36
|
from gajim.common.helpers import get_uf_affiliation
|
|
29
|
37
|
from gajim.common.helpers import jid_is_blocked
|
| ... |
... |
@@ -33,6 +41,7 @@ |
|
33
|
41
|
from gajim.gui_menu_builder import get_groupchat_roster_menu
|
|
34
|
42
|
from .tooltips import GCTooltip
|
|
35
|
43
|
from .builder import get_builder
|
|
|
44
|
+from .types import GroupchatControlType
|
|
36
|
45
|
from .util import EventHelper
|
|
37
|
46
|
|
|
38
|
47
|
|
| ... |
... |
@@ -62,7 +71,11 @@ class GroupchatRoster(Gtk.ScrolledWindow, EventHelper): |
|
62
|
71
|
(str, )) # arguments
|
|
63
|
72
|
}
|
|
64
|
73
|
|
|
65
|
|
- def __init__(self, account, room_jid, control):
|
|
|
74
|
+ def __init__(self,
|
|
|
75
|
+ account: str,
|
|
|
76
|
+ room_jid: str,
|
|
|
77
|
+ control: GroupchatControlType
|
|
|
78
|
+ ) -> None:
|
|
66
|
79
|
Gtk.ScrolledWindow.__init__(self)
|
|
67
|
80
|
EventHelper.__init__(self)
|
|
68
|
81
|
self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
| ... |
... |
@@ -74,14 +87,18 @@ def __init__(self, account, room_jid, control): |
|
74
|
87
|
self._show_roles = True
|
|
75
|
88
|
self._tooltip = GCTooltip()
|
|
76
|
89
|
self._group_chat_contact = control.contact
|
|
77
|
|
- self._group_chat_contact.connect('user-avatar-update',
|
|
78
|
|
- self._on_user_avatar_update)
|
|
|
90
|
+ self._group_chat_contact.connect(
|
|
|
91
|
+ 'user-avatar-update', self._on_user_avatar_update)
|
|
79
|
92
|
self._group_chat_contact.connect('user-joined', self._on_user_joined)
|
|
80
|
93
|
self._group_chat_contact.connect('user-left', self._on_user_left)
|
|
81
|
|
- self._group_chat_contact.connect('user-affiliation-changed', self._update_contact)
|
|
82
|
|
- self._group_chat_contact.connect('user-role-changed', self._update_contact)
|
|
83
|
|
- self._group_chat_contact.connect('user-status-show-changed', self._on_user_status_show_changed)
|
|
84
|
|
- self._group_chat_contact.connect('user-nickname-changed', self._on_user_nickname_changed)
|
|
|
94
|
+ self._group_chat_contact.connect(
|
|
|
95
|
+ 'user-affiliation-changed', self._update_contact)
|
|
|
96
|
+ self._group_chat_contact.connect(
|
|
|
97
|
+ 'user-role-changed', self._update_contact)
|
|
|
98
|
+ self._group_chat_contact.connect(
|
|
|
99
|
+ 'user-status-show-changed', self._on_user_status_show_changed)
|
|
|
100
|
+ self._group_chat_contact.connect(
|
|
|
101
|
+ 'user-nickname-changed', self._on_user_nickname_changed)
|
|
85
|
102
|
# self._group_chat_contact.connect('muc-user-presence-update',
|
|
86
|
103
|
# self._on_muc_user_presence_update)
|
|
87
|
104
|
|
| ... |
... |
@@ -90,14 +107,14 @@ def __init__(self, account, room_jid, control): |
|
90
|
107
|
self.add(self._ui.roster_treeview)
|
|
91
|
108
|
|
|
92
|
109
|
# Holds the Gtk.TreeRowReference for each contact
|
|
93
|
|
- self._contact_refs = {}
|
|
|
110
|
+ self._contact_refs: dict[str, Gtk.TreeRowReference] = {}
|
|
94
|
111
|
# Holds the Gtk.TreeRowReference for each group
|
|
95
|
|
- self._group_refs = {}
|
|
|
112
|
+ self._group_refs: dict[str, Gtk.TreeRowReference] = {}
|
|
96
|
113
|
|
|
97
|
|
- self._store = self._ui.participant_store
|
|
|
114
|
+ self._store = cast(Gtk.TreeStore, self._ui.participant_store)
|
|
98
|
115
|
self._store.set_sort_func(Column.TEXT, self._tree_compare_iters)
|
|
99
|
116
|
|
|
100
|
|
- self._roster = self._ui.roster_treeview
|
|
|
117
|
+ self._roster = cast(Gtk.TreeView, self._ui.roster_treeview)
|
|
101
|
118
|
self._roster.set_search_equal_func(self._search_func)
|
|
102
|
119
|
self._roster.set_has_tooltip(True)
|
|
103
|
120
|
self._roster.connect('query-tooltip', self._query_tooltip)
|
| ... |
... |
@@ -115,16 +132,22 @@ def __init__(self, account, room_jid, control): |
|
115
|
132
|
])
|
|
116
|
133
|
|
|
117
|
134
|
@staticmethod
|
|
118
|
|
- def _on_focus_out(treeview, _param):
|
|
|
135
|
+ def _on_focus_out(treeview: Gtk.TreeView, _param: Gdk.EventFocus) -> None:
|
|
119
|
136
|
treeview.get_selection().unselect_all()
|
|
120
|
137
|
|
|
121
|
|
- def set_model(self):
|
|
|
138
|
+ def set_model(self) -> None:
|
|
122
|
139
|
self._roster.set_model(self._store)
|
|
123
|
140
|
|
|
124
|
|
- def set_show_roles(self, enabled):
|
|
|
141
|
+ def set_show_roles(self, enabled: bool) -> None:
|
|
125
|
142
|
self._show_roles = enabled
|
|
126
|
143
|
|
|
127
|
|
- def _query_tooltip(self, widget, x_pos, y_pos, _keyboard_mode, tooltip):
|
|
|
144
|
+ def _query_tooltip(self,
|
|
|
145
|
+ widget: Gtk.Widget,
|
|
|
146
|
+ x_pos: int,
|
|
|
147
|
+ y_pos: int,
|
|
|
148
|
+ _keyboard_mode: bool,
|
|
|
149
|
+ tooltip: Gtk.Tooltip
|
|
|
150
|
+ ) -> bool:
|
|
128
|
151
|
try:
|
|
129
|
152
|
row = self._roster.get_path_at_pos(x_pos, y_pos)[0]
|
|
130
|
153
|
except TypeError:
|
| ... |
... |
@@ -154,7 +177,11 @@ def _query_tooltip(self, widget, x_pos, y_pos, _keyboard_mode, tooltip): |
|
154
|
177
|
return value
|
|
155
|
178
|
|
|
156
|
179
|
@staticmethod
|
|
157
|
|
- def _search_func(model, _column, search_text, iter_):
|
|
|
180
|
+ def _search_func(model: Gtk.TreeModel,
|
|
|
181
|
+ _column: int,
|
|
|
182
|
+ search_text: str,
|
|
|
183
|
+ iter_: Gtk.TreeIter
|
|
|
184
|
+ ) -> bool:
|
|
158
|
185
|
return search_text.lower() not in model[iter_][1].lower()
|
|
159
|
186
|
|
|
160
|
187
|
def _get_group_iter(self, group_name: str) -> Optional[Gtk.TreeIter]:
|
| ... |
... |
@@ -179,10 +206,15 @@ def _get_contact_iter(self, nick: str) -> Optional[Gtk.TreeIter]: |
|
179
|
206
|
return None
|
|
180
|
207
|
return self._store.get_iter(path)
|
|
181
|
208
|
|
|
182
|
|
- def _on_user_joined(self, _contact, _signal_name, user_contact, *args):
|
|
|
209
|
+ def _on_user_joined(self,
|
|
|
210
|
+ _contact: types.GroupchatContact,
|
|
|
211
|
+ _signal_name: str,
|
|
|
212
|
+ user_contact: types.GroupchatParticipant,
|
|
|
213
|
+ *args: Any
|
|
|
214
|
+ ) -> None:
|
|
183
|
215
|
self._add_contact(user_contact)
|
|
184
|
216
|
|
|
185
|
|
- def _add_contact(self, contact):
|
|
|
217
|
+ def _add_contact(self, contact: types.GroupchatParticipant) -> None:
|
|
186
|
218
|
group_name, group_text = self._get_group_from_contact(contact)
|
|
187
|
219
|
nick = contact.name
|
|
188
|
220
|
|
| ... |
... |
@@ -212,24 +244,44 @@ def _add_contact(self, contact): |
|
212
|
244
|
self._roster.get_model() is not None):
|
|
213
|
245
|
self._roster.expand_row(role_path, False)
|
|
214
|
246
|
|
|
215
|
|
- def _on_user_left(self, _contact, _signal_name, user_contact, *args):
|
|
|
247
|
+ def _on_user_left(self,
|
|
|
248
|
+ _contact: types.GroupchatContact,
|
|
|
249
|
+ _signal_name: str,
|
|
|
250
|
+ user_contact: types.GroupchatParticipant,
|
|
|
251
|
+ *args: Any
|
|
|
252
|
+ ) -> None:
|
|
216
|
253
|
self._remove_contact(user_contact)
|
|
217
|
254
|
self.draw_groups()
|
|
218
|
255
|
|
|
219
|
|
- def _update_contact(self, _contact, _signal_name, user_contact, *args):
|
|
|
256
|
+ def _update_contact(self,
|
|
|
257
|
+ _contact: types.GroupchatContact,
|
|
|
258
|
+ _signal_name: str,
|
|
|
259
|
+ user_contact: types.GroupchatParticipant,
|
|
|
260
|
+ *args: Any
|
|
|
261
|
+ ) -> None:
|
|
220
|
262
|
self._remove_contact(user_contact)
|
|
221
|
263
|
self._add_contact(user_contact)
|
|
222
|
264
|
|
|
223
|
|
- def _on_user_nickname_changed(self, contact, _signal_name, user_contact, properties):
|
|
|
265
|
+ def _on_user_nickname_changed(self,
|
|
|
266
|
+ contact: types.GroupchatContact,
|
|
|
267
|
+ _signal_name: str,
|
|
|
268
|
+ user_contact: types.GroupchatParticipant,
|
|
|
269
|
+ properties: MessageProperties
|
|
|
270
|
+ ) -> None:
|
|
224
|
271
|
self._remove_contact(user_contact)
|
|
225
|
272
|
|
|
226
|
273
|
user_contact = contact.get_resource(properties.muc_user.nick)
|
|
227
|
274
|
self._add_contact(user_contact)
|
|
228
|
275
|
|
|
229
|
|
- def _on_user_status_show_changed(self, _contact, _signal_name, user_contact, *args):
|
|
|
276
|
+ def _on_user_status_show_changed(self,
|
|
|
277
|
+ _contact: types.GroupchatContact,
|
|
|
278
|
+ _signal_name: str,
|
|
|
279
|
+ user_contact: types.GroupchatParticipant,
|
|
|
280
|
+ *args: Any
|
|
|
281
|
+ ) -> None:
|
|
230
|
282
|
self.draw_contact(user_contact.name)
|
|
231
|
283
|
|
|
232
|
|
- def _remove_contact(self, contact):
|
|
|
284
|
+ def _remove_contact(self, contact: types.GroupchatParticipant) -> None:
|
|
233
|
285
|
nick = contact.name
|
|
234
|
286
|
iter_ = self._get_contact_iter(nick)
|
|
235
|
287
|
if not iter_:
|
| ... |
... |
@@ -247,33 +299,43 @@ def _remove_contact(self, contact): |
|
247
|
299
|
self._store.remove(group_iter)
|
|
248
|
300
|
|
|
249
|
301
|
@staticmethod
|
|
250
|
|
- def _get_group_from_contact(contact):
|
|
|
302
|
+ def _get_group_from_contact(contact: types.GroupchatParticipant
|
|
|
303
|
+ ) -> Tuple[str, str]:
|
|
251
|
304
|
if contact.affiliation in (Affiliation.OWNER, Affiliation.ADMIN):
|
|
252
|
305
|
return contact.affiliation.value, get_uf_affiliation(
|
|
253
|
306
|
contact.affiliation, plural=True)
|
|
254
|
307
|
return contact.role.value, get_uf_role(contact.role, plural=True)
|
|
255
|
308
|
|
|
256
|
309
|
@staticmethod
|
|
257
|
|
- def _text_cell_data_func(_column, renderer, model, iter_, _user_data):
|
|
|
310
|
+ def _text_cell_data_func(_column: Gtk.TreeViewColumn,
|
|
|
311
|
+ renderer: Gtk.CellRenderer,
|
|
|
312
|
+ model: Gtk.TreeModel,
|
|
|
313
|
+ iter_: Gtk.TreeIter,
|
|
|
314
|
+ _user_data: Optional[object]
|
|
|
315
|
+ ) -> None:
|
|
258
|
316
|
has_parent = bool(model.iter_parent(iter_))
|
|
259
|
317
|
style = 'contact' if has_parent else 'group'
|
|
260
|
318
|
|
|
261
|
|
- bgcolor = app.css_config.get_value('.gajim-%s-row' % style,
|
|
|
319
|
+ bgcolor = app.css_config.get_value(f'.gajim-{style}-row',
|
|
262
|
320
|
StyleAttr.BACKGROUND)
|
|
263
|
321
|
renderer.set_property('cell-background', bgcolor)
|
|
264
|
322
|
|
|
265
|
|
- color = app.css_config.get_value('.gajim-%s-row' % style,
|
|
|
323
|
+ color = app.css_config.get_value(f'.gajim-{style}-row',
|
|
266
|
324
|
StyleAttr.COLOR)
|
|
267
|
325
|
renderer.set_property('foreground', color)
|
|
268
|
326
|
|
|
269
|
|
- desc = app.css_config.get_font('.gajim-%s-row' % style)
|
|
|
327
|
+ desc = app.css_config.get_font(f'.gajim-{style}-row')
|
|
270
|
328
|
renderer.set_property('font-desc', desc)
|
|
271
|
329
|
|
|
272
|
330
|
if not has_parent:
|
|
273
|
331
|
renderer.set_property('weight', 600)
|
|
274
|
332
|
renderer.set_property('ypad', 6)
|
|
275
|
333
|
|
|
276
|
|
- def _on_roster_row_activated(self, _treeview, path, _column):
|
|
|
334
|
+ def _on_roster_row_activated(self,
|
|
|
335
|
+ _treeview: Gtk.TreeView,
|
|
|
336
|
+ path: Gtk.TreePath,
|
|
|
337
|
+ _column: Gtk.TreeViewColumn
|
|
|
338
|
+ ) -> None:
|
|
277
|
339
|
iter_ = self._store.get_iter(path)
|
|
278
|
340
|
if self._store.iter_parent(iter_) is None:
|
|
279
|
341
|
# This is a group row
|
| ... |
... |
@@ -283,7 +345,10 @@ def _on_roster_row_activated(self, _treeview, path, _column): |
|
283
|
345
|
return
|
|
284
|
346
|
self.emit('row-activated', nick)
|
|
285
|
347
|
|
|
286
|
|
- def _on_roster_button_press_event(self, treeview, event):
|
|
|
348
|
+ def _on_roster_button_press_event(self,
|
|
|
349
|
+ treeview: Gtk.TreeView,
|
|
|
350
|
+ event: Gdk.EventButton
|
|
|
351
|
+ ) -> None:
|
|
287
|
352
|
if event.button not in (2, 3):
|
|
288
|
353
|
return
|
|
289
|
354
|
|
| ... |
... |
@@ -307,7 +372,7 @@ def _on_roster_button_press_event(self, treeview, event): |
|
307
|
372
|
if event.button == 2: # middle click
|
|
308
|
373
|
self.emit('row-activated', nick)
|
|
309
|
374
|
|
|
310
|
|
- def _show_contact_menu(self, nick):
|
|
|
375
|
+ def _show_contact_menu(self, nick: str) -> None:
|
|
311
|
376
|
self_contact = self._group_chat_contact.get_self()
|
|
312
|
377
|
contact = self._group_chat_contact.get_resource(nick)
|
|
313
|
378
|
menu = get_groupchat_roster_menu(self._account,
|
| ... |
... |
@@ -324,11 +389,15 @@ def destroy(menu, _pspec): |
|
324
|
389
|
menu.connect('notify::visible', destroy)
|
|
325
|
390
|
menu.popup_at_pointer()
|
|
326
|
391
|
|
|
327
|
|
- def _tree_compare_iters(self, model, iter1, iter2, _user_data):
|
|
|
392
|
+ def _tree_compare_iters(self,
|
|
|
393
|
+ model: Gtk.TreeModel,
|
|
|
394
|
+ iter1: Gtk.TreeIter,
|
|
|
395
|
+ iter2: Gtk.TreeIter,
|
|
|
396
|
+ _user_data: Optional[object]
|
|
|
397
|
+ ) -> int:
|
|
328
|
398
|
"""
|
|
329
|
399
|
Compare two iterators to sort them
|
|
330
|
400
|
"""
|
|
331
|
|
-
|
|
332
|
401
|
is_contact = model.iter_parent(iter1)
|
|
333
|
402
|
if is_contact:
|
|
334
|
403
|
# Sort contacts with pending events to top
|
| ... |
... |
@@ -356,28 +425,28 @@ def _tree_compare_iters(self, model, iter1, iter2, _user_data): |
|
356
|
425
|
group2_index = AffiliationRoleSortOrder[group2]
|
|
357
|
426
|
return -1 if group1_index < group2_index else 1
|
|
358
|
427
|
|
|
359
|
|
- def enable_sort(self, enable):
|
|
|
428
|
+ def enable_sort(self, enable: bool) -> None:
|
|
360
|
429
|
column = Gtk.TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
|
|
361
|
430
|
if enable:
|
|
362
|
431
|
column = Column.TEXT
|
|
363
|
432
|
|
|
364
|
433
|
self._store.set_sort_column_id(column, Gtk.SortType.ASCENDING)
|
|
365
|
434
|
|
|
366
|
|
- def invalidate_sort(self):
|
|
|
435
|
+ def invalidate_sort(self) -> None:
|
|
367
|
436
|
self.enable_sort(False)
|
|
368
|
437
|
self.enable_sort(True)
|
|
369
|
438
|
|
|
370
|
|
- def initial_draw(self):
|
|
|
439
|
+ def initial_draw(self) -> None:
|
|
371
|
440
|
self.enable_sort(True)
|
|
372
|
441
|
self.set_model()
|
|
373
|
442
|
self._roster.expand_all()
|
|
374
|
443
|
|
|
375
|
|
- def redraw(self):
|
|
|
444
|
+ def redraw(self) -> None:
|
|
376
|
445
|
self._roster.set_model(None)
|
|
377
|
446
|
self._roster.set_model(self._store)
|
|
378
|
447
|
self._roster.expand_all()
|
|
379
|
448
|
|
|
380
|
|
- def draw_contact(self, nick):
|
|
|
449
|
+ def draw_contact(self, nick: str) -> None:
|
|
381
|
450
|
iter_ = self._get_contact_iter(nick)
|
|
382
|
451
|
if not iter_:
|
|
383
|
452
|
return
|
| ... |
... |
@@ -391,9 +460,9 @@ def draw_contact(self, nick): |
|
391
|
460
|
name = GLib.markup_escape_text(contact.name)
|
|
392
|
461
|
|
|
393
|
462
|
# Strike name if blocked
|
|
394
|
|
- fjid = self.room_jid + '/' + nick
|
|
|
463
|
+ fjid = f'{self.room_jid}/{nick}'
|
|
395
|
464
|
if jid_is_blocked(self._account, fjid):
|
|
396
|
|
- name = '<span strikethrough="true">%s</span>' % name
|
|
|
465
|
+ name = f'<span strikethrough="true">{name}</span>'
|
|
397
|
466
|
|
|
398
|
467
|
# add status msg, if not empty, under contact name
|
|
399
|
468
|
status = contact.status
|
| ... |
... |
@@ -404,16 +473,16 @@ def draw_contact(self, nick): |
|
404
|
473
|
# Display only first line
|
|
405
|
474
|
status = status.split('\n', 1)[0]
|
|
406
|
475
|
# escape markup entities and make them small italic and fg color
|
|
407
|
|
- name += ('\n<span size="small" style="italic" alpha="70%">'
|
|
408
|
|
- '{}</span>'.format(GLib.markup_escape_text(status)))
|
|
|
476
|
+ name += (f'\n<span size="small" style="italic" alpha="70%">'
|
|
|
477
|
+ f'{GLib.markup_escape_text(status)}</span>')
|
|
409
|
478
|
|
|
410
|
479
|
self._store[iter_][Column.TEXT] = name
|
|
411
|
480
|
|
|
412
|
|
- def draw_contacts(self):
|
|
|
481
|
+ def draw_contacts(self) -> None:
|
|
413
|
482
|
for nick in self._contact_refs:
|
|
414
|
483
|
self.draw_contact(nick)
|
|
415
|
484
|
|
|
416
|
|
- def draw_group(self, group):
|
|
|
485
|
+ def draw_group(self, group: str) -> None:
|
|
417
|
486
|
group_iter = self._get_group_iter(group)
|
|
418
|
487
|
if not group_iter:
|
|
419
|
488
|
return
|
| ... |
... |
@@ -426,15 +495,15 @@ def draw_group(self, group): |
|
426
|
495
|
total_users = self._get_total_user_count()
|
|
427
|
496
|
group_users = self._store.iter_n_children(group_iter)
|
|
428
|
497
|
|
|
429
|
|
- group_text += ' (%s/%s)' % (group_users, total_users)
|
|
|
498
|
+ group_text += f' ({group_users}/{total_users})'
|
|
430
|
499
|
|
|
431
|
500
|
self._store[group_iter][Column.TEXT] = group_text
|
|
432
|
501
|
|
|
433
|
|
- def draw_groups(self):
|
|
|
502
|
+ def draw_groups(self) -> None:
|
|
434
|
503
|
for group in self._group_refs:
|
|
435
|
504
|
self.draw_group(group)
|
|
436
|
505
|
|
|
437
|
|
- def draw_avatar(self, contact):
|
|
|
506
|
+ def draw_avatar(self, contact: types.GroupchatParticipant) -> None:
|
|
438
|
507
|
iter_ = self._get_contact_iter(contact.name)
|
|
439
|
508
|
if iter_ is None:
|
|
440
|
509
|
return
|
| ... |
... |
@@ -446,13 +515,13 @@ def draw_avatar(self, contact): |
|
446
|
515
|
def _on_user_avatar_update(self, _contact, _signal_name, user_contact):
|
|
447
|
516
|
self.draw_avatar(user_contact)
|
|
448
|
517
|
|
|
449
|
|
- def _get_total_user_count(self):
|
|
|
518
|
+ def _get_total_user_count(self) -> int:
|
|
450
|
519
|
count = 0
|
|
451
|
520
|
for group_row in self._store:
|
|
452
|
521
|
count += self._store.iter_n_children(group_row.iter)
|
|
453
|
522
|
return count
|
|
454
|
523
|
|
|
455
|
|
- def get_role(self, nick):
|
|
|
524
|
+ def get_role(self, nick: str) -> Role:
|
|
456
|
525
|
contact = self._group_chat_contact.get_resource(nick)
|
|
457
|
526
|
return contact.role
|
|
458
|
527
|
|
| ... |
... |
@@ -462,12 +531,12 @@ def _on_theme_update(self, _event): |
|
462
|
531
|
def process_avatar_update(self, event):
|
|
463
|
532
|
self.draw_avatar(event.contact)
|
|
464
|
533
|
|
|
465
|
|
- def clear(self):
|
|
|
534
|
+ def clear(self) -> None:
|
|
466
|
535
|
self._contact_refs = {}
|
|
467
|
536
|
self._group_refs = {}
|
|
468
|
537
|
self._store.clear()
|
|
469
|
538
|
|
|
470
|
|
- def _on_destroy(self, _roster):
|
|
|
539
|
+ def _on_destroy(self, _roster: Any) -> None:
|
|
471
|
540
|
self._group_chat_contact.disconnect(self)
|
|
472
|
541
|
|
|
473
|
542
|
self._contact_refs = {}
|