| ... |
... |
@@ -22,11 +22,11 @@ |
|
22
|
22
|
import logging
|
|
23
|
23
|
import time
|
|
24
|
24
|
import pickle
|
|
25
|
|
-import operator
|
|
26
|
25
|
|
|
27
|
26
|
from gi.repository import Gio
|
|
28
|
27
|
from gi.repository import Gdk
|
|
29
|
28
|
from gi.repository import GLib
|
|
|
29
|
+from gi.repository import GObject
|
|
30
|
30
|
from gi.repository import Gtk
|
|
31
|
31
|
import cairo
|
|
32
|
32
|
|
| ... |
... |
@@ -67,6 +67,13 @@ |
|
67
|
67
|
|
|
68
|
68
|
|
|
69
|
69
|
class ChatList(Gtk.ListBox, EventHelper):
|
|
|
70
|
+
|
|
|
71
|
+ __gsignals__ = {
|
|
|
72
|
+ 'chat-order-changed': (GObject.SignalFlags.RUN_LAST,
|
|
|
73
|
+ None,
|
|
|
74
|
+ ()),
|
|
|
75
|
+ }
|
|
|
76
|
+
|
|
70
|
77
|
def __init__(self, workspace_id: str) -> None:
|
|
71
|
78
|
Gtk.ListBox.__init__(self)
|
|
72
|
79
|
EventHelper.__init__(self)
|
| ... |
... |
@@ -75,7 +82,6 @@ def __init__(self, workspace_id: str) -> None: |
|
75
|
82
|
self._chats: dict[tuple[str, JID], Any] = {}
|
|
76
|
83
|
self._current_filter: str = 'all'
|
|
77
|
84
|
self._current_filter_text: str = ''
|
|
78
|
|
- self._chat_order: dict[tuple[str, JID], int] = {}
|
|
79
|
85
|
|
|
80
|
86
|
self.get_style_context().add_class('chatlist')
|
|
81
|
87
|
self.set_filter_func(self._filter_func)
|
| ... |
... |
@@ -97,12 +103,16 @@ def __init__(self, workspace_id: str) -> None: |
|
97
|
103
|
entries,
|
|
98
|
104
|
Gdk.DragAction.MOVE)
|
|
99
|
105
|
|
|
|
106
|
+ self._drag_row: Optional[ChatRow] = None
|
|
|
107
|
+ self._chat_order: list[ChatRow] = []
|
|
|
108
|
+
|
|
100
|
109
|
self.register_events([
|
|
101
|
110
|
('account-enabled', ged.GUI2, self._on_account_changed),
|
|
102
|
111
|
('account-disabled', ged.GUI2, self._on_account_changed),
|
|
103
|
112
|
('bookmarks-received', ged.GUI1, self._on_bookmarks_received),
|
|
104
|
113
|
])
|
|
105
|
114
|
|
|
|
115
|
+ self.connect('drag-data-received', self._on_drag_data_received)
|
|
106
|
116
|
self.connect('destroy', self._on_destroy)
|
|
107
|
117
|
|
|
108
|
118
|
self._timer_id = GLib.timeout_add_seconds(60, self._update_timer)
|
| ... |
... |
@@ -149,6 +159,94 @@ def emit_unread_changed(self) -> None: |
|
149
|
159
|
self._workspace_id,
|
|
150
|
160
|
count)
|
|
151
|
161
|
|
|
|
162
|
+ def _get_row_before(self, row: ChatRow) -> Optional[ChatRow]:
|
|
|
163
|
+ row = cast(ChatRow, self.get_row_at_index(row.get_index() - 1))
|
|
|
164
|
+ return row
|
|
|
165
|
+
|
|
|
166
|
+ def _get_row_after(self, row: ChatRow) -> Optional[ChatRow]:
|
|
|
167
|
+ row = cast(ChatRow, self.get_row_at_index(row.get_index() + 1))
|
|
|
168
|
+ return row
|
|
|
169
|
+
|
|
|
170
|
+ def _get_last_row(self) -> ChatRow:
|
|
|
171
|
+ row = cast(ChatRow, self.get_row_at_index(len(self.get_children())))
|
|
|
172
|
+ return row
|
|
|
173
|
+
|
|
|
174
|
+ def set_drag_row(self, row: ChatRow) -> None:
|
|
|
175
|
+ self._drag_row = row
|
|
|
176
|
+
|
|
|
177
|
+ def _on_drag_data_received(self,
|
|
|
178
|
+ _widget: Gtk.Widget,
|
|
|
179
|
+ _drag_context: Gdk.DragContext,
|
|
|
180
|
+ _x_coord: int,
|
|
|
181
|
+ y_coord: int,
|
|
|
182
|
+ selection_data: Gtk.SelectionData,
|
|
|
183
|
+ _info: int,
|
|
|
184
|
+ _time: int
|
|
|
185
|
+ ) -> None:
|
|
|
186
|
+
|
|
|
187
|
+ item_type = selection_data.get_data_type().name()
|
|
|
188
|
+ if item_type == 'CHAT_LIST_ITEM':
|
|
|
189
|
+ row = cast(ChatRow, self.get_row_at_y(y_coord))
|
|
|
190
|
+ if row:
|
|
|
191
|
+ alloc = row.get_allocation()
|
|
|
192
|
+ hover_row_y = alloc.y
|
|
|
193
|
+ hover_row_height = alloc.height
|
|
|
194
|
+
|
|
|
195
|
+ if y_coord < hover_row_y + hover_row_height / 2:
|
|
|
196
|
+ row_before = self._get_row_before(row)
|
|
|
197
|
+ row_after = row
|
|
|
198
|
+ else:
|
|
|
199
|
+ row_before = row
|
|
|
200
|
+ row_after = self._get_row_after(row)
|
|
|
201
|
+ else:
|
|
|
202
|
+ row_before = self._get_last_row()
|
|
|
203
|
+ row_after = None
|
|
|
204
|
+
|
|
|
205
|
+ if self._drag_row in (row_before, row_after):
|
|
|
206
|
+ return
|
|
|
207
|
+
|
|
|
208
|
+ self._change_pinned_order(row_before, row_after)
|
|
|
209
|
+ else:
|
|
|
210
|
+ log.debug('Unknown item type dropped')
|
|
|
211
|
+
|
|
|
212
|
+ def _change_pinned_order(self,
|
|
|
213
|
+ row_before: Optional[ChatRow],
|
|
|
214
|
+ row_after: Optional[ChatRow]
|
|
|
215
|
+ ) -> None:
|
|
|
216
|
+
|
|
|
217
|
+ assert self._drag_row is not None
|
|
|
218
|
+
|
|
|
219
|
+ if not self._drag_row.is_pinned:
|
|
|
220
|
+ log.debug('Dropped row is not pinned')
|
|
|
221
|
+ return
|
|
|
222
|
+
|
|
|
223
|
+ if self._drag_row in (row_before, row_after):
|
|
|
224
|
+ # TODO: already checked this case!?
|
|
|
225
|
+ log.debug('Dropped row on self')
|
|
|
226
|
+ return
|
|
|
227
|
+
|
|
|
228
|
+ if row_after is None:
|
|
|
229
|
+ log.debug('Dropped row into empty space')
|
|
|
230
|
+ return
|
|
|
231
|
+
|
|
|
232
|
+ self._chat_order.remove(self._drag_row)
|
|
|
233
|
+
|
|
|
234
|
+ if row_before is None:
|
|
|
235
|
+ self._chat_order.insert(0, self._drag_row)
|
|
|
236
|
+ else:
|
|
|
237
|
+ if row_before.position < self._drag_row.position:
|
|
|
238
|
+ offset = 1
|
|
|
239
|
+ else:
|
|
|
240
|
+ offset = 0
|
|
|
241
|
+ self._chat_order.insert(
|
|
|
242
|
+ row_before.position + offset, self._drag_row)
|
|
|
243
|
+
|
|
|
244
|
+ for row in self._chat_order:
|
|
|
245
|
+ row.position = self._chat_order.index(row)
|
|
|
246
|
+
|
|
|
247
|
+ self.emit('chat-order-changed')
|
|
|
248
|
+ self.invalidate_sort()
|
|
|
249
|
+
|
|
152
|
250
|
def _on_destroy(self, _widget: Gtk.Widget) -> None:
|
|
153
|
251
|
GLib.source_remove(self._timer_id)
|
|
154
|
252
|
|
| ... |
... |
@@ -273,10 +371,11 @@ def add_chat(self, |
|
273
|
371
|
jid,
|
|
274
|
372
|
type_,
|
|
275
|
373
|
pinned,
|
|
276
|
|
- self._chat_order)
|
|
|
374
|
+ position)
|
|
277
|
375
|
|
|
278
|
|
- self._chat_order[key] = position
|
|
279
|
376
|
self._chats[key] = row
|
|
|
377
|
+ if position != -1:
|
|
|
378
|
+ self._chat_order.insert(position, row)
|
|
280
|
379
|
|
|
281
|
380
|
self.add(row)
|
|
282
|
381
|
|
| ... |
... |
@@ -353,6 +452,16 @@ def select_chat_number(self, number: int) -> None: |
|
353
|
452
|
def toggle_chat_pinned(self, account: str, jid: JID) -> None:
|
|
354
|
453
|
row = self._chats[(account, jid)]
|
|
355
|
454
|
row.toggle_pinned()
|
|
|
455
|
+
|
|
|
456
|
+ self._chat_order.clear()
|
|
|
457
|
+ chat_rows = cast(list[ChatRow], self.get_children())
|
|
|
458
|
+ for row in chat_rows:
|
|
|
459
|
+ if row.is_pinned:
|
|
|
460
|
+ row.position = chat_rows.index(row)
|
|
|
461
|
+ self._chat_order.append(row)
|
|
|
462
|
+ else:
|
|
|
463
|
+ row.position = -1
|
|
|
464
|
+
|
|
356
|
465
|
self.invalidate_sort()
|
|
357
|
466
|
|
|
358
|
467
|
def remove_chat(self,
|
| ... |
... |
@@ -362,6 +471,8 @@ def remove_chat(self, |
|
362
|
471
|
) -> None:
|
|
363
|
472
|
|
|
364
|
473
|
row = self._chats.pop((account, jid))
|
|
|
474
|
+ if row.position != -1:
|
|
|
475
|
+ self._chat_order.remove(row)
|
|
365
|
476
|
self.remove(row)
|
|
366
|
477
|
row.destroy()
|
|
367
|
478
|
if emit_unread:
|
| ... |
... |
@@ -563,29 +674,6 @@ def _on_bookmarks_received(self, _event: events.BookmarksReceived) -> None: |
|
563
|
674
|
for row in rows:
|
|
564
|
675
|
row.update_name()
|
|
565
|
676
|
|
|
566
|
|
- def _change_pinned_order(self,
|
|
567
|
|
- row: ChatRow,
|
|
568
|
|
- row_after: Optional[ChatRow]) -> None:
|
|
569
|
|
-
|
|
570
|
|
- # Make list ordered by position of tuple[account, JID]
|
|
571
|
|
- order = [item[0] for item in sorted(self._chat_order.items(),
|
|
572
|
|
- key=operator.itemgetter(1))]
|
|
573
|
|
-
|
|
574
|
|
- key = (row.account, row.jid)
|
|
575
|
|
-
|
|
576
|
|
- order.remove(key)
|
|
577
|
|
- if row_after is None or not row_after.is_pinned:
|
|
578
|
|
- order.append(key)
|
|
579
|
|
- else:
|
|
580
|
|
- pos = order.index((row_after.account, row_after.jid))
|
|
581
|
|
- order.insert(pos, key)
|
|
582
|
|
-
|
|
583
|
|
- self._chat_order.clear()
|
|
584
|
|
- for pos, value in enumerate(order):
|
|
585
|
|
- self._chat_order[value] = pos
|
|
586
|
|
-
|
|
587
|
|
- self.invalidate_sort()
|
|
588
|
|
-
|
|
589
|
677
|
|
|
590
|
678
|
class ChatRow(Gtk.ListBoxRow):
|
|
591
|
679
|
def __init__(self,
|
| ... |
... |
@@ -594,7 +682,7 @@ def __init__(self, |
|
594
|
682
|
jid: JID,
|
|
595
|
683
|
type_: str,
|
|
596
|
684
|
pinned: bool,
|
|
597
|
|
- position: dict[tuple[str, JID], int]
|
|
|
685
|
+ position: int
|
|
598
|
686
|
) -> None:
|
|
599
|
687
|
|
|
600
|
688
|
Gtk.ListBoxRow.__init__(self)
|
| ... |
... |
@@ -767,7 +855,11 @@ def is_pinned(self) -> bool: |
|
767
|
855
|
|
|
768
|
856
|
@property
|
|
769
|
857
|
def position(self) -> int:
|
|
770
|
|
- return self._position.get((self.account, self.jid), -1)
|
|
|
858
|
+ return self._position
|
|
|
859
|
+
|
|
|
860
|
+ @position.setter
|
|
|
861
|
+ def position(self, position: int) -> None:
|
|
|
862
|
+ self._position = position
|
|
771
|
863
|
|
|
772
|
864
|
def _on_button_press(self,
|
|
773
|
865
|
_widget: Gtk.Widget,
|
| ... |
... |
@@ -799,6 +891,10 @@ def _on_drag_begin(self, |
|
799
|
891
|
widget: Gtk.Widget,
|
|
800
|
892
|
drag_context: Gdk.DragContext
|
|
801
|
893
|
) -> None:
|
|
|
894
|
+
|
|
|
895
|
+ chat_list = cast(ChatList, self.get_parent())
|
|
|
896
|
+ chat_list.set_drag_row(self)
|
|
|
897
|
+
|
|
802
|
898
|
# Use rendered ChatListRow as drag icon
|
|
803
|
899
|
alloc = self.get_allocation()
|
|
804
|
900
|
surface = cairo.ImageSurface(
|