""" FileListWidget — file list based on ttk.Treeview with dark theme styling. """ import tkinter as tk from tkinter import ttk import customtkinter as ctk _THEME_APPLIED = False def _apply_dark_theme(): global _THEME_APPLIED if _THEME_APPLIED: return _THEME_APPLIED = True style = ttk.Style() style.theme_use("clam") style.configure( "Dark.Treeview", background="#1e1e1e", foreground="#dcdcdc", fieldbackground="#1e1e1e", borderwidth=0, font=("Consolas", 11), rowheight=24, ) style.configure( "Dark.Treeview.Heading", background="#2b2b2b", foreground="#9ca3af", borderwidth=0, font=("Segoe UI", 10, "bold"), relief="flat", ) style.map( "Dark.Treeview", background=[("selected", "#3b82f6")], foreground=[("selected", "#ffffff")], ) style.map( "Dark.Treeview.Heading", background=[("active", "#333333")], ) style.layout("Dark.Treeview", [ ("Dark.Treeview.treearea", {"sticky": "nswe"}), ]) class FileListWidget(ctk.CTkFrame): """File list with columns, sorting, multi-selection.""" def __init__(self, master, columns: list[tuple[str, int]], on_navigate=None, on_select=None): super().__init__(master, fg_color="#1e1e1e", corner_radius=6) self._on_navigate = on_navigate self._on_select = on_select self._items: list[dict] = [] self._sort_col = None self._sort_reverse = False _apply_dark_theme() col_ids = [c[0] for c in columns] self._tree = ttk.Treeview( self, columns=col_ids, show="headings", selectmode="extended", style="Dark.Treeview", ) for col_name, col_width in columns: self._tree.heading( col_name, text=col_name, anchor="w", command=lambda c=col_name: self._sort_by_column(c), ) self._tree.column(col_name, width=col_width, minwidth=40, anchor="w") scrollbar = ctk.CTkScrollbar(self, command=self._tree.yview) self._tree.configure(yscrollcommand=scrollbar.set) self._tree.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") self._tree.bind("", self._on_double_click) self._tree.bind("<>", self._on_tree_select) self._tree.bind("", self._on_enter) def populate(self, items: list[dict]): """Fill list with items. Each item: {name, size, date, perm, is_dir}.""" self._items = items self._tree.delete(*self._tree.get_children()) dirs = [i for i in items if i.get("is_dir")] files = [i for i in items if not i.get("is_dir")] dirs.sort(key=lambda x: x["name"].lower()) files.sort(key=lambda x: x["name"].lower()) for item in dirs + files: prefix = "\U0001F4C1 " if item.get("is_dir") else "\U0001F4C4 " cols = self._tree.cget("columns") values = [] for c in cols: if c == cols[0]: # Name column values.append(prefix + item.get("name", "")) elif c.lower() in ("size", "sizes"): values.append(item.get("size", "")) elif c.lower() in ("date", "dates"): values.append(item.get("date", "")) elif c.lower() in ("perm", "perms"): values.append(item.get("perm", "")) else: values.append(item.get(c.lower(), "")) iid = self._tree.insert("", "end", values=values) self._tree.item(iid, tags=("dir",) if item.get("is_dir") else ("file",)) def get_selected(self) -> list[dict]: """Return selected items as dicts.""" result = [] for iid in self._tree.selection(): idx = self._tree.index(iid) dirs = [i for i in self._items if i.get("is_dir")] files = [i for i in self._items if not i.get("is_dir")] dirs.sort(key=lambda x: x["name"].lower()) files.sort(key=lambda x: x["name"].lower()) ordered = dirs + files if 0 <= idx < len(ordered): result.append(ordered[idx]) return result def _on_double_click(self, event): sel = self.get_selected() if sel and sel[0].get("is_dir") and self._on_navigate: self._on_navigate(sel[0]["name"]) def _on_enter(self, event): sel = self.get_selected() if sel and sel[0].get("is_dir") and self._on_navigate: self._on_navigate(sel[0]["name"]) def _on_tree_select(self, event): if self._on_select: self._on_select(self.get_selected()) def _sort_by_column(self, col): if self._sort_col == col: self._sort_reverse = not self._sort_reverse else: self._sort_col = col self._sort_reverse = False cols = self._tree.cget("columns") col_map = {cols[0]: "name"} for c in cols[1:]: col_map[c] = c.lower() field = col_map.get(col, "name") dirs = [i for i in self._items if i.get("is_dir")] files = [i for i in self._items if not i.get("is_dir")] dirs.sort(key=lambda x: str(x.get(field, "")).lower(), reverse=self._sort_reverse) files.sort(key=lambda x: str(x.get(field, "")).lower(), reverse=self._sort_reverse) self._items = dirs + files self.populate(self._items)