v1.8.0: file manager improvements
- SFTP cleanup on app close and language switch - Windows drive selector in local panel - Browse and Refresh buttons for local panel - Recursive upload/download/delete of folders - Drag-and-drop between local and remote panels - Sudo mode toggle for privileged file operations - New i18n keys for EN/RU/ZH Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -49,17 +49,23 @@ def _apply_dark_theme():
|
||||
|
||||
|
||||
class FileListWidget(ctk.CTkFrame):
|
||||
"""File list with columns, sorting, multi-selection."""
|
||||
"""File list with columns, sorting, multi-selection, drag-and-drop."""
|
||||
|
||||
def __init__(self, master, columns: list[tuple[str, int]],
|
||||
on_navigate=None, on_select=None):
|
||||
on_navigate=None, on_select=None, on_drop=None):
|
||||
super().__init__(master, fg_color="#1e1e1e", corner_radius=6)
|
||||
|
||||
self._on_navigate = on_navigate
|
||||
self._on_select = on_select
|
||||
self._on_drop = on_drop
|
||||
self._items: list[dict] = []
|
||||
self._sort_col = None
|
||||
self._sort_reverse = False
|
||||
self._drag_partner: "FileListWidget | None" = None
|
||||
self._drag_start_x = 0
|
||||
self._drag_start_y = 0
|
||||
self._dragging = False
|
||||
self._orig_fg_color = "#1e1e1e"
|
||||
|
||||
_apply_dark_theme()
|
||||
|
||||
@@ -91,6 +97,11 @@ class FileListWidget(ctk.CTkFrame):
|
||||
self._tree.bind("<<TreeviewSelect>>", self._on_tree_select)
|
||||
self._tree.bind("<Return>", self._on_enter)
|
||||
|
||||
# Drag-and-drop bindings
|
||||
self._tree.bind("<ButtonPress-1>", self._on_drag_start, add="+")
|
||||
self._tree.bind("<B1-Motion>", self._on_drag_motion)
|
||||
self._tree.bind("<ButtonRelease-1>", self._on_drag_release, add="+")
|
||||
|
||||
def populate(self, items: list[dict]):
|
||||
"""Fill list with items. Each item: {name, size, date, perm, is_dir}."""
|
||||
self._items = items
|
||||
@@ -168,3 +179,46 @@ class FileListWidget(ctk.CTkFrame):
|
||||
|
||||
self._items = dirs + files
|
||||
self.populate(self._items)
|
||||
|
||||
# ── Drag-and-drop ──
|
||||
|
||||
def set_drag_partner(self, partner: "FileListWidget"):
|
||||
"""Link two panels for drag-and-drop."""
|
||||
self._drag_partner = partner
|
||||
|
||||
def _on_drag_start(self, event):
|
||||
self._drag_start_x = event.x_root
|
||||
self._drag_start_y = event.y_root
|
||||
self._dragging = False
|
||||
|
||||
def _on_drag_motion(self, event):
|
||||
if not self._drag_partner:
|
||||
return
|
||||
dx = abs(event.x_root - self._drag_start_x)
|
||||
dy = abs(event.y_root - self._drag_start_y)
|
||||
if not self._dragging and (dx > 10 or dy > 10):
|
||||
sel = self.get_selected()
|
||||
if sel and any(s["name"] != ".." for s in sel):
|
||||
self._dragging = True
|
||||
self._tree.configure(cursor="hand2")
|
||||
self._drag_partner.configure(fg_color="#1e3a5f")
|
||||
|
||||
def _on_drag_release(self, event):
|
||||
if not self._dragging or not self._drag_partner:
|
||||
self._dragging = False
|
||||
return
|
||||
self._dragging = False
|
||||
self._tree.configure(cursor="")
|
||||
self._drag_partner.configure(fg_color=self._orig_fg_color)
|
||||
|
||||
# Check if mouse is over partner widget
|
||||
px = self._drag_partner.winfo_rootx()
|
||||
py = self._drag_partner.winfo_rooty()
|
||||
pw = self._drag_partner.winfo_width()
|
||||
ph = self._drag_partner.winfo_height()
|
||||
mx, my = event.x_root, event.y_root
|
||||
|
||||
if px <= mx <= px + pw and py <= my <= py + ph:
|
||||
items = [s for s in self.get_selected() if s["name"] != ".."]
|
||||
if items and self._drag_partner._on_drop:
|
||||
self._drag_partner._on_drop(items, self)
|
||||
|
||||
Reference in New Issue
Block a user