Daniel Brötzmann pushed to branch pinned-chat-dnd at gajim / gajim
Commits: c321404f by wurstsalat at 2022-08-19T18:06:58+02:00 feature: ChatList: Add drag and drop for pinned chats
- - - - -
2 changed files:
- gajim/gtk/chat_list.py - gajim/gtk/main.py
Changes:
===================================== gajim/gtk/chat_list.py ===================================== @@ -22,7 +22,6 @@ import logging import time import pickle -import operator
from gi.repository import Gio from gi.repository import Gdk @@ -75,7 +74,6 @@ def __init__(self, workspace_id: str) -> None: self._chats: dict[tuple[str, JID], Any] = {} self._current_filter: str = 'all' self._current_filter_text: str = '' - self._chat_order: dict[tuple[str, JID], int] = {}
self.get_style_context().add_class('chatlist') self.set_filter_func(self._filter_func) @@ -97,12 +95,19 @@ def __init__(self, workspace_id: str) -> None: entries, Gdk.DragAction.MOVE)
+ self._drag_row: Optional[ChatRow] = None + self._row_before: Optional[ChatRow] = None + self._row_after: Optional[ChatRow] = None + self._chat_order: list[ChatRow] = [] + self.register_events([ ('account-enabled', ged.GUI2, self._on_account_changed), ('account-disabled', ged.GUI2, self._on_account_changed), ('bookmarks-received', ged.GUI1, self._on_bookmarks_received), ])
+ self.connect('drag-motion', self._on_drag_motion) + self.connect('drag-data-received', self._on_drag_data_received) self.connect('destroy', self._on_destroy)
self._timer_id = GLib.timeout_add_seconds(60, self._update_timer) @@ -149,6 +154,100 @@ def emit_unread_changed(self) -> None: self._workspace_id, count)
+ def _get_row_before(self, row: ChatRow) -> Optional[ChatRow]: + row = cast(ChatRow, self.get_row_at_index(row.get_index() - 1)) + return row + + def _get_row_after(self, row: ChatRow) -> Optional[ChatRow]: + row = cast(ChatRow, self.get_row_at_index(row.get_index() + 1)) + return row + + def _get_last_row(self) -> ChatRow: + row = cast(ChatRow, self.get_row_at_index(len(self.get_children()))) + return row + + def set_drag_row(self, row: ChatRow) -> None: + self._drag_row = row + + def _on_drag_motion(self, + _widget: Gtk.Widget, + _drag_context: Gdk.DragContext, + _x_coord: int, + y_coord: int, + _time: int + ) -> bool: + + row = cast(ChatRow, self.get_row_at_y(y_coord)) + if row: + alloc = row.get_allocation() + hover_row_y = alloc.y + hover_row_height = alloc.height + + if y_coord < hover_row_y + hover_row_height / 2: + self._row_before = self._get_row_before(row) + self._row_after = row + else: + self._row_before = row + self._row_after = self._get_row_after(row) + else: + self._row_before = self._get_last_row() + self._row_after = None + + if self._drag_row in (self._row_before, self._row_after): + return False + + return True + + def _on_drag_data_received(self, + _widget: Gtk.Widget, + _drag_context: Gdk.DragContext, + _x_coord: int, + y_coord: int, + selection_data: Gtk.SelectionData, + _info: int, + _time: int + ) -> None: + + item_type = selection_data.get_data_type().name() + if item_type == 'CHAT_LIST_ITEM': + self._change_pinned_order() + else: + log.debug('Unknown item type dropped') + + def _change_pinned_order(self) -> None: + assert self._drag_row is not None + + if not self._drag_row.is_pinned: + log.debug('Dragged row is not pinned') + return + + if self._drag_row in (self._row_before, self._row_after): + log.debug('Dragged row dropped on self') + return + + if self._row_after is None: + log.debug('Dragged row dropped in empty space') + return + + self._chat_order.remove(self._drag_row) + + if self._row_before is None: + self._chat_order.insert(0, self._drag_row) + else: + if self._row_before.position < self._drag_row.position: + offset = 1 + else: + offset = 0 + self._chat_order.insert( + self._row_before.position + offset, self._drag_row) + + for row in cast(list[ChatRow], self.get_children()): + if row in self._chat_order: + row.position = self._chat_order.index(row) + + app.window.store_open_chats(self._workspace_id) + self.invalidate_sort() + def _on_destroy(self, _widget: Gtk.Widget) -> None: GLib.source_remove(self._timer_id)
@@ -273,10 +372,11 @@ def add_chat(self, jid, type_, pinned, - self._chat_order) + position)
- self._chat_order[key] = position self._chats[key] = row + if position != -1: + self._chat_order.insert(position, row)
self.add(row)
@@ -353,6 +453,16 @@ def select_chat_number(self, number: int) -> None: def toggle_chat_pinned(self, account: str, jid: JID) -> None: row = self._chats[(account, jid)] row.toggle_pinned() + + self._chat_order.clear() + chat_rows = cast(list[ChatRow], self.get_children()) + for row in chat_rows: + if row.is_pinned: + row.position = chat_rows.index(row) + self._chat_order.append(row) + else: + row.position = -1 + self.invalidate_sort()
def remove_chat(self, @@ -362,6 +472,8 @@ def remove_chat(self, ) -> None:
row = self._chats.pop((account, jid)) + if row.position != -1: + self._chat_order.remove(row) self.remove(row) row.destroy() if emit_unread: @@ -563,29 +675,6 @@ def _on_bookmarks_received(self, _event: events.BookmarksReceived) -> None: for row in rows: row.update_name()
- def _change_pinned_order(self, - row: ChatRow, - row_after: Optional[ChatRow]) -> None: - - # Make list ordered by position of tuple[account, JID] - order = [item[0] for item in sorted(self._chat_order.items(), - key=operator.itemgetter(1))] - - key = (row.account, row.jid) - - order.remove(key) - if row_after is None or not row_after.is_pinned: - order.append(key) - else: - pos = order.index((row_after.account, row_after.jid)) - order.insert(pos, key) - - self._chat_order.clear() - for pos, value in enumerate(order): - self._chat_order[value] = pos - - self.invalidate_sort() -
class ChatRow(Gtk.ListBoxRow): def __init__(self, @@ -594,7 +683,7 @@ def __init__(self, jid: JID, type_: str, pinned: bool, - position: dict[tuple[str, JID], int] + position: int ) -> None:
Gtk.ListBoxRow.__init__(self) @@ -767,7 +856,11 @@ def is_pinned(self) -> bool:
@property def position(self) -> int: - return self._position.get((self.account, self.jid), -1) + return self._position + + @position.setter + def position(self, position: int) -> None: + self._position = position
def _on_button_press(self, _widget: Gtk.Widget, @@ -799,6 +892,10 @@ def _on_drag_begin(self, widget: Gtk.Widget, drag_context: Gdk.DragContext ) -> None: + + chat_list = cast(ChatList, self.get_parent()) + chat_list.set_drag_row(self) + # Use rendered ChatListRow as drag icon alloc = self.get_allocation() surface = cairo.ImageSurface(
===================================== gajim/gtk/main.py ===================================== @@ -654,6 +654,10 @@ def update_workspace(self, workspace_id: str) -> None: self._chat_page.update_workspace(workspace_id) self._workspace_side_bar.update_avatar(workspace_id)
+ def store_open_chats(self, workspace_id: str) -> None: + chat_list_stack = self._chat_page.get_chat_list_stack() + chat_list_stack.store_open_chats(workspace_id) + def get_chat_list(self, workspace_id: str) -> ChatList: chat_list_stack = self._chat_page.get_chat_list_stack() return chat_list_stack.get_chatlist(workspace_id)
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/c321404fc5d66713613754367a09553e7...