commit 55
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
"""App configuration for Custom File Dialog"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import os
|
||||
from typing import Dict, Any
|
||||
@@ -13,8 +14,7 @@ class AppConfig:
|
||||
This class serves as a singleton-like container for all global configuration data,
|
||||
including paths, UI settings, localization, versioning, and system-specific resources.
|
||||
It ensures that required directories, files, and services are created and configured
|
||||
before the application starts. Additionally, it provides tools for managing translations,
|
||||
default settings, and autostart functionality to maintain a consistent user experience.
|
||||
before the application starts. Additionally, it provides tools for managing translations.
|
||||
|
||||
Key Responsibilities:
|
||||
- Centralizes all configuration values (paths, UI preferences, localization).
|
||||
@@ -35,15 +35,6 @@ class AppConfig:
|
||||
BASE_DIR: Path = Path.home()
|
||||
CONFIG_DIR: Path = BASE_DIR / ".config/cfiledialog"
|
||||
|
||||
# Configuration files
|
||||
SETTINGS_FILE: Path = CONFIG_DIR / "settings"
|
||||
DEFAULT_SETTINGS: Dict[str, str] = {
|
||||
"# Configuration": "on",
|
||||
"# Theme": "dark",
|
||||
"# Tooltips": True,
|
||||
"# Autostart": "off",
|
||||
}
|
||||
|
||||
# UI configuration
|
||||
UI_CONFIG: Dict[str, Any] = {
|
||||
"window_size": (1050, 850),
|
||||
@@ -53,23 +44,6 @@ class AppConfig:
|
||||
"resizable_window": (True, True),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def ensure_directories(cls) -> None:
|
||||
"""Ensures that all required directories exist"""
|
||||
if not cls.CONFIG_DIR.exists():
|
||||
cls.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@classmethod
|
||||
def create_default_settings(cls) -> None:
|
||||
"""Creates default settings if they don't exist"""
|
||||
if not cls.SETTINGS_FILE.exists():
|
||||
content = "\n".join(
|
||||
f"[{k.upper()}]\n{v}" for k, v in cls.DEFAULT_SETTINGS.items()
|
||||
)
|
||||
cls.SETTINGS_FILE.write_text(content)
|
||||
|
||||
|
||||
import json
|
||||
|
||||
# here is initializing the class for translation strings
|
||||
_ = Translate.setup_translations("custom_file_fialog")
|
||||
@@ -85,7 +59,10 @@ class CfdConfigManager:
|
||||
"search_icon_pos": "left", # 'left' or 'right'
|
||||
"button_box_pos": "left", # 'left' or 'right'
|
||||
"window_size_preset": "1050x850", # e.g., "1050x850"
|
||||
"default_view_mode": "icons" # 'icons' or 'list'
|
||||
"default_view_mode": "icons", # 'icons' or 'list'
|
||||
"search_hidden_files": False, # True or False
|
||||
"use_trash": False, # True or False
|
||||
"confirm_delete": False # True or False
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
201
cfd_ui_setup.py
201
cfd_ui_setup.py
@@ -104,7 +104,8 @@ class StyleManager:
|
||||
style.map("Bottom.TButton.Borderless.Round",
|
||||
background=[('active', self.hover_extrastyle)])
|
||||
style.layout("Bottom.TButton.Borderless.Round",
|
||||
style.layout("Header.TButton.Borderless.Round"))
|
||||
style.layout("Header.TButton.Borderless.Round")
|
||||
)
|
||||
|
||||
|
||||
class WidgetManager:
|
||||
@@ -125,12 +126,12 @@ class WidgetManager:
|
||||
top_bar = ttk.Frame(
|
||||
main_frame, style='Accent.TFrame', padding=(0, 5, 0, 5))
|
||||
top_bar.grid(row=0, column=0, columnspan=2, sticky="ew")
|
||||
# Make path entry column expandable
|
||||
top_bar.grid_columnconfigure(2, weight=1)
|
||||
|
||||
# Left navigation buttons
|
||||
left_nav_container = ttk.Frame(top_bar, style='Accent.TFrame')
|
||||
left_nav_container.grid(row=0, column=0, sticky="w")
|
||||
# Prevent this container from changing size
|
||||
left_nav_container.grid_propagate(False)
|
||||
|
||||
self.back_button = ttk.Button(left_nav_container, image=self.dialog.icon_manager.get_icon(
|
||||
'back'), command=self.dialog.go_back, state=tk.DISABLED, style="Header.TButton.Borderless.Round")
|
||||
@@ -142,85 +143,105 @@ class WidgetManager:
|
||||
self.forward_button.pack(side="left", padx=5)
|
||||
Tooltip(self.forward_button, "Vorwärts")
|
||||
|
||||
self.up_button = ttk.Button(left_nav_container, image=self.dialog.icon_manager.get_icon(
|
||||
'up'), command=self.dialog.go_up_level, style="Header.TButton.Borderless.Round")
|
||||
self.up_button.pack(side="left", padx=5)
|
||||
Tooltip(self.up_button, "Eine Ebene höher")
|
||||
|
||||
self.home_button = ttk.Button(left_nav_container, image=self.dialog.icon_manager.get_icon(
|
||||
'home'), command=lambda: self.dialog.navigate_to(os.path.expanduser("~")), style="Header.TButton.Borderless.Round")
|
||||
self.home_button.pack(side="left", padx=(5, 10))
|
||||
Tooltip(self.home_button, "Home")
|
||||
|
||||
# Search button (left position)
|
||||
search_icon_pos = self.settings.get("search_icon_pos", "left")
|
||||
if search_icon_pos == 'left':
|
||||
search_container_left = ttk.Frame(top_bar, style='Accent.TFrame')
|
||||
search_container_left.grid(row=0, column=1, sticky="w")
|
||||
self.search_button = ttk.Button(search_container_left, image=self.dialog.icon_manager.get_icon(
|
||||
'search_small'), command=self.dialog.toggle_search_mode, style="Header.TButton.Borderless.Round")
|
||||
self.search_button.pack(side="left", padx=5)
|
||||
Tooltip(self.search_button, "Suchen")
|
||||
# Path and search widgets container
|
||||
path_search_container = ttk.Frame(top_bar, style='Accent.TFrame')
|
||||
path_search_container.grid(row=0, column=1, sticky="ew")
|
||||
|
||||
# Right-side controls container
|
||||
right_controls_container = ttk.Frame(top_bar, style='Accent.TFrame')
|
||||
right_controls_container.grid(row=0, column=2, sticky="e")
|
||||
|
||||
# Make the middle column (path_search_container) expand
|
||||
top_bar.grid_columnconfigure(1, weight=1)
|
||||
|
||||
search_icon_pos = self.settings.get("search_icon_pos", "left")
|
||||
self.recursive_search = tk.BooleanVar(value=True)
|
||||
self.recursive_button = ttk.Button(search_container_left, image=self.dialog.icon_manager.get_icon(
|
||||
'recursive_small'), command=self.dialog.toggle_recursive_search, style="Header.TButton.Active.Round")
|
||||
self.recursive_button.pack(side="left", padx=2)
|
||||
self.recursive_button.pack_forget() # Initially hidden
|
||||
Tooltip(self.recursive_button, "Rekursive Suche ein/ausschalten")
|
||||
|
||||
# Path entry
|
||||
self.path_entry = ttk.Entry(top_bar)
|
||||
self.path_entry.grid(row=0, column=2, sticky="ew")
|
||||
self.path_entry = ttk.Entry(path_search_container)
|
||||
self.path_entry.bind(
|
||||
"<Return>", lambda e: self.dialog.navigate_to(self.path_entry.get()))
|
||||
|
||||
# Right-side controls
|
||||
right_controls_container = ttk.Frame(top_bar, style='Accent.TFrame')
|
||||
right_controls_container.grid(row=0, column=3, sticky="e")
|
||||
|
||||
# Search button (right position)
|
||||
if search_icon_pos == 'right':
|
||||
search_container_right = ttk.Frame(
|
||||
right_controls_container, style='Accent.TFrame')
|
||||
search_container_right.pack(side="left", padx=5)
|
||||
self.search_button = ttk.Button(search_container_right, image=self.dialog.icon_manager.get_icon(
|
||||
# Function to create search widgets
|
||||
def create_search_widgets(parent_frame):
|
||||
container = ttk.Frame(parent_frame, style='Accent.TFrame')
|
||||
self.search_button = ttk.Button(container, image=self.dialog.icon_manager.get_icon(
|
||||
'search_small'), command=self.dialog.toggle_search_mode, style="Header.TButton.Borderless.Round")
|
||||
self.search_button.pack(side="left")
|
||||
Tooltip(self.search_button, "Suchen")
|
||||
|
||||
self.recursive_search = tk.BooleanVar(value=True)
|
||||
self.recursive_button = ttk.Button(search_container_right, image=self.dialog.icon_manager.get_icon(
|
||||
self.recursive_button = ttk.Button(container, image=self.dialog.icon_manager.get_icon(
|
||||
'recursive_small'), command=self.dialog.toggle_recursive_search, style="Header.TButton.Active.Round")
|
||||
self.recursive_button.pack(side="left", padx=2)
|
||||
self.recursive_button.pack_forget() # Initially hidden
|
||||
Tooltip(self.recursive_button, "Rekursive Suche ein/ausschalten")
|
||||
return container
|
||||
|
||||
# Other right-side buttons
|
||||
self.new_folder_button = ttk.Button(right_controls_container, image=self.dialog.icon_manager.get_icon(
|
||||
# Place search and path entry based on settings
|
||||
if search_icon_pos == 'left':
|
||||
path_search_container.grid_columnconfigure(1, weight=1)
|
||||
search_container = create_search_widgets(path_search_container)
|
||||
search_container.grid(row=0, column=0, sticky="w", padx=(0, 5))
|
||||
self.path_entry.grid(row=0, column=1, sticky="ew")
|
||||
else: # right
|
||||
path_search_container.grid_columnconfigure(0, weight=1)
|
||||
search_container = create_search_widgets(path_search_container)
|
||||
search_container.grid(row=0, column=1, sticky="e", padx=(5, 0))
|
||||
self.path_entry.grid(row=0, column=0, sticky="ew")
|
||||
|
||||
# --- Responsive Buttons ---
|
||||
self.responsive_buttons_container = ttk.Frame(
|
||||
right_controls_container, style='Accent.TFrame')
|
||||
self.responsive_buttons_container.pack(side="left")
|
||||
|
||||
self.new_folder_button = ttk.Button(self.responsive_buttons_container, image=self.dialog.icon_manager.get_icon(
|
||||
'new_folder_small'), command=self.dialog.create_new_folder, style="Header.TButton.Borderless.Round")
|
||||
self.new_folder_button.pack(side="left", padx=5)
|
||||
Tooltip(self.new_folder_button, "Neuen Ordner erstellen")
|
||||
|
||||
self.new_file_button = ttk.Button(right_controls_container, image=self.dialog.icon_manager.get_icon(
|
||||
self.new_file_button = ttk.Button(self.responsive_buttons_container, image=self.dialog.icon_manager.get_icon(
|
||||
'new_document_small'), command=self.dialog.create_new_file, style="Header.TButton.Borderless.Round")
|
||||
self.new_file_button.pack(side="left", padx=5)
|
||||
Tooltip(self.new_file_button, "Neues Dokument erstellen")
|
||||
|
||||
view_switch = ttk.Frame(right_controls_container,
|
||||
padding=(5, 0), style='Accent.TFrame')
|
||||
view_switch.pack(side="left")
|
||||
if self.dialog.dialog_mode == "open":
|
||||
self.new_folder_button.config(state=tk.DISABLED)
|
||||
self.new_file_button.config(state=tk.DISABLED)
|
||||
|
||||
self.icon_view_button = ttk.Button(view_switch, image=self.dialog.icon_manager.get_icon(
|
||||
self.view_switch = ttk.Frame(self.responsive_buttons_container,
|
||||
padding=(5, 0), style='Accent.TFrame')
|
||||
self.view_switch.pack(side="left")
|
||||
|
||||
self.icon_view_button = ttk.Button(self.view_switch, image=self.dialog.icon_manager.get_icon(
|
||||
'icon_view'), command=self.dialog.set_icon_view, style="Header.TButton.Active.Round")
|
||||
self.icon_view_button.pack(side="left", padx=5)
|
||||
Tooltip(self.icon_view_button, "Kachelansicht")
|
||||
|
||||
self.list_view_button = ttk.Button(view_switch, image=self.dialog.icon_manager.get_icon(
|
||||
self.list_view_button = ttk.Button(self.view_switch, image=self.dialog.icon_manager.get_icon(
|
||||
'list_view'), command=self.dialog.set_list_view, style="Header.TButton.Borderless.Round")
|
||||
self.list_view_button.pack(side="left")
|
||||
Tooltip(self.list_view_button, "Listenansicht")
|
||||
|
||||
self.hidden_files_button = ttk.Button(right_controls_container, image=self.dialog.icon_manager.get_icon(
|
||||
self.hidden_files_button = ttk.Button(self.responsive_buttons_container, image=self.dialog.icon_manager.get_icon(
|
||||
'hide'), command=self.dialog.toggle_hidden_files, style="Header.TButton.Borderless.Round")
|
||||
self.hidden_files_button.pack(side="left", padx=10)
|
||||
Tooltip(self.hidden_files_button, "Versteckte Dateien anzeigen")
|
||||
|
||||
# "More" button for responsive UI
|
||||
self.more_button = ttk.Button(right_controls_container, text="...",
|
||||
command=self.dialog.show_more_menu, style="Header.TButton.Borderless.Round", width=3)
|
||||
# self.more_button is managed by _handle_responsive_buttons
|
||||
|
||||
# Horizontal separator
|
||||
separator_color = "#000000" if self.style_manager.is_dark else "#9c9c9c"
|
||||
tk.Frame(main_frame, height=1, bg=separator_color).grid(
|
||||
@@ -374,71 +395,67 @@ class WidgetManager:
|
||||
content_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.file_list_frame = ttk.Frame(
|
||||
content_frame, style="AccentBottom.TFrame")
|
||||
# Use Content.TFrame for consistent bg color
|
||||
content_frame, style="Content.TFrame")
|
||||
self.file_list_frame.grid(row=0, column=0, sticky="nsew")
|
||||
self.dialog.bind("<Configure>", self.dialog.on_window_resize)
|
||||
|
||||
bottom_controls_frame = ttk.Frame(
|
||||
# This frame will contain the action buttons and status bar
|
||||
self.action_status_frame = ttk.Frame(
|
||||
content_frame, style="AccentBottom.TFrame")
|
||||
bottom_controls_frame.grid(row=1, column=0, sticky="ew", pady=(5, 0))
|
||||
self.action_status_frame.grid(
|
||||
row=1, column=0, sticky="ew", pady=(5, 10), padx=10)
|
||||
|
||||
button_box_pos = self.settings.get("button_box_pos", "left")
|
||||
action_buttons_col = 0 if button_box_pos == 'left' else 2
|
||||
action_buttons_sticky = "w" if button_box_pos == 'left' else "e"
|
||||
|
||||
if self.dialog.dialog_mode == "save":
|
||||
# Give most of the weight to the action buttons frame (which contains the entry)
|
||||
bottom_controls_frame.grid_columnconfigure(action_buttons_col, weight=10)
|
||||
bottom_controls_frame.grid_columnconfigure(1, weight=1) # status_bar is in col 1
|
||||
# Configure columns for the action_status_frame
|
||||
if button_box_pos == 'left':
|
||||
self.action_status_frame.grid_columnconfigure(1, weight=1)
|
||||
else:
|
||||
# Original behavior for open mode
|
||||
bottom_controls_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
action_buttons_frame = ttk.Frame(
|
||||
bottom_controls_frame, style="AccentBottom.TFrame")
|
||||
action_buttons_frame.grid(
|
||||
row=0, column=action_buttons_col, rowspan=2, sticky="nsew", pady=(5, 10))
|
||||
self.action_status_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# Status bar will be placed inside the action_status_frame
|
||||
self.status_bar = ttk.Label(
|
||||
bottom_controls_frame, text="", anchor="w", style="AccentBottom.TLabel")
|
||||
status_bar_col = 1 if button_box_pos == 'left' else 1
|
||||
status_bar_sticky = "w" if button_box_pos == 'left' else "e"
|
||||
self.status_bar.grid(row=0, column=status_bar_col,
|
||||
sticky=status_bar_sticky, padx=10)
|
||||
self.action_status_frame, text="", anchor="w", style="AccentBottom.TLabel")
|
||||
|
||||
self.settings_button = ttk.Button(bottom_controls_frame, image=self.dialog.icon_manager.get_icon(
|
||||
self.settings_button = ttk.Button(self.action_status_frame, image=self.dialog.icon_manager.get_icon(
|
||||
'settings-2_small'), command=self.dialog.open_settings_dialog, style="Bottom.TButton.Borderless.Round")
|
||||
self.settings_button.grid(
|
||||
row=0, column=3, sticky="ne", padx=(0, 5), pady=(2, 0))
|
||||
Tooltip(self.settings_button, "Einstellungen")
|
||||
|
||||
self.trash_button = ttk.Button(self.action_status_frame, image=self.dialog.icon_manager.get_icon(
|
||||
'trash_small2'), command=self.dialog.delete_selected_item, style="Bottom.TButton.Borderless.Round")
|
||||
Tooltip(self.trash_button, "Ausgewähltes Element löschen/verschieben")
|
||||
|
||||
if self.dialog.dialog_mode == "save":
|
||||
self.filename_entry = ttk.Entry(action_buttons_frame)
|
||||
self.filename_entry = ttk.Entry(self.action_status_frame)
|
||||
save_button = ttk.Button(
|
||||
action_buttons_frame, text="Speichern", command=self.dialog.on_save)
|
||||
self.action_status_frame, text="Speichern", command=self.dialog.on_save)
|
||||
cancel_button = ttk.Button(
|
||||
action_buttons_frame, text="Abbrechen", command=self.dialog.on_cancel)
|
||||
self.filter_combobox = ttk.Combobox(action_buttons_frame, values=[
|
||||
self.action_status_frame, text="Abbrechen", command=self.dialog.on_cancel)
|
||||
self.filter_combobox = ttk.Combobox(self.action_status_frame, values=[
|
||||
ft[0] for ft in self.dialog.filetypes], state="readonly")
|
||||
|
||||
if button_box_pos == 'left':
|
||||
action_buttons_frame.grid_columnconfigure(1, weight=1)
|
||||
save_button.grid(row=0, column=0, sticky="w", padx=(0, 10))
|
||||
self.filename_entry.grid(
|
||||
row=0, column=1, sticky="ew", padx=(10, 0))
|
||||
save_button.grid(row=0, column=0, sticky="e", padx=(10, 5))
|
||||
row=0, column=1, sticky="ew", padx=(0, 5))
|
||||
cancel_button.grid(row=1, column=0, sticky="w",
|
||||
padx=(10, 0), pady=(10, 0))
|
||||
pady=(5, 0), padx=(0, 10))
|
||||
self.filter_combobox.grid(
|
||||
row=1, column=1, sticky="w", padx=(10, 0), pady=(10, 0))
|
||||
row=1, column=1, sticky="w", pady=(5, 0), padx=(0, 5))
|
||||
self.settings_button.grid(row=0, column=3, sticky="e")
|
||||
self.trash_button.grid(
|
||||
row=1, column=3, sticky="se", padx=(5, 0))
|
||||
else: # right
|
||||
action_buttons_frame.grid_columnconfigure(0, weight=1)
|
||||
save_button.grid(row=0, column=1, sticky="w", padx=(5, 5))
|
||||
self.trash_button.grid(
|
||||
row=1, column=0, sticky="sw", padx=(0, 5))
|
||||
self.filename_entry.grid(
|
||||
row=0, column=0, sticky="ew", padx=(0, 10))
|
||||
row=0, column=1, sticky="ew", padx=(0, 5))
|
||||
self.filter_combobox.grid(
|
||||
row=1, column=0, sticky="e", padx=(0, 10), pady=(10, 0))
|
||||
cancel_button.grid(row=1, column=1, sticky="e",
|
||||
padx=(0, 5), pady=(10, 0))
|
||||
row=1, column=1, sticky="e", pady=(5, 0), padx=(0, 5))
|
||||
save_button.grid(row=0, column=2, sticky="e", padx=5)
|
||||
cancel_button.grid(row=1, column=2, sticky="e",
|
||||
padx=5, pady=(5, 0))
|
||||
self.settings_button.grid(row=0, column=3, sticky="e")
|
||||
|
||||
self.filter_combobox.bind(
|
||||
"<<ComboboxSelected>>", self.dialog.on_filter_change)
|
||||
@@ -446,24 +463,28 @@ class WidgetManager:
|
||||
|
||||
else: # Open mode
|
||||
open_button = ttk.Button(
|
||||
action_buttons_frame, text="Öffnen", command=self.dialog.on_open)
|
||||
self.action_status_frame, text="Öffnen", command=self.dialog.on_open)
|
||||
cancel_button = ttk.Button(
|
||||
action_buttons_frame, text="Abbrechen", command=self.dialog.on_cancel)
|
||||
self.filter_combobox = ttk.Combobox(action_buttons_frame, values=[
|
||||
self.action_status_frame, text="Abbrechen", command=self.dialog.on_cancel)
|
||||
self.filter_combobox = ttk.Combobox(self.action_status_frame, values=[
|
||||
ft[0] for ft in self.dialog.filetypes], state="readonly")
|
||||
|
||||
if button_box_pos == 'left':
|
||||
open_button.grid(row=0, column=0, sticky="e", padx=(10, 5))
|
||||
cancel_button.grid(row=1, column=0, sticky="w",
|
||||
padx=(10, 0), pady=(10, 0))
|
||||
open_button.grid(row=0, column=0, sticky="w", padx=(0, 5))
|
||||
self.status_bar.grid(row=0, column=1, sticky="w", padx=5)
|
||||
cancel_button.grid(row=1, column=0, sticky="w", pady=(5, 0))
|
||||
self.filter_combobox.grid(
|
||||
row=1, column=1, sticky="w", padx=(10, 0), pady=(10, 0))
|
||||
row=1, column=1, sticky="w", pady=(5, 0), padx=(5, 0))
|
||||
self.settings_button.grid(row=0, column=2, sticky="e")
|
||||
|
||||
else: # right
|
||||
open_button.grid(row=0, column=1, sticky="w", padx=(5, 5))
|
||||
self.status_bar.grid(row=0, column=0, sticky="e", padx=10)
|
||||
open_button.grid(row=0, column=1, sticky="e", padx=5)
|
||||
cancel_button.grid(row=1, column=1, sticky="e",
|
||||
padx=(0, 5), pady=(10, 0))
|
||||
padx=5, pady=(5, 0))
|
||||
self.filter_combobox.grid(
|
||||
row=1, column=0, sticky="e", padx=(0, 5), pady=(10, 0))
|
||||
row=1, column=0, sticky="e", pady=(5, 0))
|
||||
self.settings_button.grid(row=0, column=2, sticky="e")
|
||||
|
||||
self.filter_combobox.bind(
|
||||
"<<ComboboxSelected>>", self.dialog.on_filter_change)
|
||||
|
@@ -10,15 +10,22 @@ from shared_libs.common_tools import IconManager, Tooltip, ConfigManager, LxTool
|
||||
from cfd_app_config import AppConfig, CfdConfigManager
|
||||
from cfd_ui_setup import StyleManager, WidgetManager, get_xdg_user_dir
|
||||
|
||||
try:
|
||||
import send2trash
|
||||
SEND2TRASH_AVAILABLE = True
|
||||
except ImportError:
|
||||
SEND2TRASH_AVAILABLE = False
|
||||
|
||||
|
||||
class SettingsDialog(tk.Toplevel):
|
||||
def __init__(self, parent):
|
||||
def __init__(self, parent, dialog_mode="save"):
|
||||
super().__init__(parent)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
self.title("Einstellungen")
|
||||
|
||||
self.settings = CfdConfigManager.load()
|
||||
self.dialog_mode = dialog_mode
|
||||
|
||||
# Variables
|
||||
self.search_icon_pos = tk.StringVar(
|
||||
@@ -29,6 +36,12 @@ class SettingsDialog(tk.Toplevel):
|
||||
value=self.settings.get("window_size_preset", "1050x850"))
|
||||
self.default_view_mode = tk.StringVar(
|
||||
value=self.settings.get("default_view_mode", "icons"))
|
||||
self.search_hidden_files = tk.BooleanVar(
|
||||
value=self.settings.get("search_hidden_files", False))
|
||||
self.use_trash = tk.BooleanVar(
|
||||
value=self.settings.get("use_trash", False))
|
||||
self.confirm_delete = tk.BooleanVar(
|
||||
value=self.settings.get("confirm_delete", False))
|
||||
|
||||
# --- UI Elements ---
|
||||
main_frame = ttk.Frame(self, padding=10)
|
||||
@@ -70,6 +83,39 @@ class SettingsDialog(tk.Toplevel):
|
||||
ttk.Radiobutton(view_mode_frame, text="Liste",
|
||||
variable=self.default_view_mode, value="list").pack(side="left", padx=5)
|
||||
|
||||
# Search Hidden Files
|
||||
search_hidden_frame = ttk.LabelFrame(
|
||||
main_frame, text="Sucheinstellungen", padding=10)
|
||||
search_hidden_frame.pack(fill="x", pady=5)
|
||||
ttk.Checkbutton(search_hidden_frame, text="Versteckte Dateien und Ordner durchsuchen",
|
||||
variable=self.search_hidden_files).pack(anchor="w")
|
||||
|
||||
# Deletion Settings
|
||||
delete_frame = ttk.LabelFrame(
|
||||
main_frame, text="Löscheinstellungen", padding=10)
|
||||
delete_frame.pack(fill="x", pady=5)
|
||||
|
||||
self.use_trash_checkbutton = ttk.Checkbutton(delete_frame, text="Dateien in den Papierkorb verschieben (empfohlen)",
|
||||
variable=self.use_trash)
|
||||
self.use_trash_checkbutton.pack(anchor="w")
|
||||
|
||||
if not SEND2TRASH_AVAILABLE:
|
||||
self.use_trash_checkbutton.config(state=tk.DISABLED)
|
||||
ttk.Label(delete_frame, text="(send2trash-Bibliothek nicht gefunden)",
|
||||
font=("TkDefaultFont", 9, "italic")).pack(anchor="w", padx=(20, 0))
|
||||
|
||||
self.confirm_delete_checkbutton = ttk.Checkbutton(delete_frame, text="Löschen/Verschieben ohne Bestätigung",
|
||||
variable=self.confirm_delete)
|
||||
self.confirm_delete_checkbutton.pack(anchor="w")
|
||||
|
||||
# Disable deletion options in "open" mode
|
||||
if self.dialog_mode == "open":
|
||||
self.use_trash_checkbutton.config(state=tk.DISABLED)
|
||||
self.confirm_delete_checkbutton.config(state=tk.DISABLED)
|
||||
info_label = ttk.Label(delete_frame, text="(Löschoptionen sind nur im Speichern-Modus verfügbar)",
|
||||
font=("TkDefaultFont", 9, "italic"))
|
||||
info_label.pack(anchor="w", padx=(20, 0))
|
||||
|
||||
# --- Action Buttons ---
|
||||
button_frame = ttk.Frame(main_frame)
|
||||
button_frame.pack(fill="x", pady=(10, 0))
|
||||
@@ -86,7 +132,10 @@ class SettingsDialog(tk.Toplevel):
|
||||
"search_icon_pos": self.search_icon_pos.get(),
|
||||
"button_box_pos": self.button_box_pos.get(),
|
||||
"window_size_preset": self.window_size_preset.get(),
|
||||
"default_view_mode": self.default_view_mode.get()
|
||||
"default_view_mode": self.default_view_mode.get(),
|
||||
"search_hidden_files": self.search_hidden_files.get(),
|
||||
"use_trash": self.use_trash.get(),
|
||||
"confirm_delete": self.confirm_delete.get()
|
||||
}
|
||||
CfdConfigManager.save(new_settings)
|
||||
self.master.reload_config_and_rebuild_ui()
|
||||
@@ -98,6 +147,9 @@ class SettingsDialog(tk.Toplevel):
|
||||
self.button_box_pos.set(defaults["button_box_pos"])
|
||||
self.window_size_preset.set(defaults["window_size_preset"])
|
||||
self.default_view_mode.set(defaults["default_view_mode"])
|
||||
self.search_hidden_files.set(defaults["search_hidden_files"])
|
||||
self.use_trash.set(defaults["use_trash"])
|
||||
self.confirm_delete.set(defaults["confirm_delete"])
|
||||
|
||||
|
||||
class CustomFileDialog(tk.Toplevel):
|
||||
@@ -141,14 +193,55 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.original_path_text = "" # Store original path text
|
||||
self.items_to_load_per_batch = 250
|
||||
self.item_path_map = {}
|
||||
self.responsive_buttons_hidden = None # State for responsive buttons
|
||||
|
||||
self.icon_manager = IconManager()
|
||||
self.style_manager = StyleManager(self)
|
||||
self.widget_manager = WidgetManager(self, self.settings)
|
||||
|
||||
self._update_view_mode_buttons()
|
||||
|
||||
# Defer initial navigation until the window geometry is calculated
|
||||
# to ensure the icon view gets the correct initial width.
|
||||
def initial_load():
|
||||
# Force layout update to get correct widths
|
||||
self.update_idletasks()
|
||||
self.last_width = self.widget_manager.file_list_frame.winfo_width()
|
||||
self._handle_responsive_buttons(self.winfo_width())
|
||||
self.navigate_to(self.current_dir)
|
||||
|
||||
# Using after(10) gives the window manager a moment to process
|
||||
# the initial window drawing and sizing.
|
||||
self.after(10, initial_load)
|
||||
|
||||
# Bind the intelligent return handler
|
||||
self.widget_manager.path_entry.bind("<Return>", self.handle_path_entry_return)
|
||||
|
||||
# Bind the delete key only in "save" mode
|
||||
if self.dialog_mode == "save":
|
||||
self.bind("<Delete>", self.delete_selected_item)
|
||||
|
||||
def handle_path_entry_return(self, event):
|
||||
"""Intelligently handles the Enter key in the path entry.
|
||||
|
||||
If the text is a valid directory, it navigates there.
|
||||
Otherwise, if in search mode, it executes a search.
|
||||
"""
|
||||
path_text = self.widget_manager.path_entry.get().strip()
|
||||
|
||||
# Try to interpret as a path first
|
||||
# Expand user-home and resolve relative paths
|
||||
potential_path = os.path.realpath(os.path.expanduser(path_text))
|
||||
|
||||
if os.path.isdir(potential_path):
|
||||
# If search was active, turn it off before navigating
|
||||
if self.search_mode:
|
||||
self.toggle_search_mode()
|
||||
self.navigate_to(potential_path)
|
||||
elif self.search_mode:
|
||||
# If not a valid path and in search mode, execute search
|
||||
self.execute_search(event)
|
||||
|
||||
def load_settings(self):
|
||||
self.settings = CfdConfigManager.load()
|
||||
size_preset = self.settings.get("window_size_preset", "1050x850")
|
||||
@@ -179,10 +272,20 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.style_manager = StyleManager(self)
|
||||
self.widget_manager = WidgetManager(self, self.settings)
|
||||
self._update_view_mode_buttons()
|
||||
|
||||
# Reset responsive button state and re-evaluate
|
||||
self.responsive_buttons_hidden = None
|
||||
self.update_idletasks()
|
||||
self._handle_responsive_buttons(self.winfo_width())
|
||||
|
||||
# If search was active, reset it to avoid inconsistent state
|
||||
if self.search_mode:
|
||||
self.toggle_search_mode() # This will correctly reset the UI
|
||||
|
||||
self.navigate_to(self.current_dir)
|
||||
|
||||
def open_settings_dialog(self):
|
||||
SettingsDialog(self)
|
||||
SettingsDialog(self, dialog_mode=self.dialog_mode)
|
||||
|
||||
def get_file_icon(self, filename, size='large'):
|
||||
ext = os.path.splitext(filename)[1].lower()
|
||||
@@ -196,7 +299,8 @@ class CustomFileDialog(tk.Toplevel):
|
||||
if ext in ['.mp3', '.wav', '.ogg', '.flac']:
|
||||
return self.icon_manager.get_icon(f'audio_{size}')
|
||||
if ext in ['.mp4', '.mkv', '.avi', '.mov']:
|
||||
return self.icon_manager.get_icon(f'video_{size}') if size == 'large' else self.icon_manager.get_icon('video_small_file')
|
||||
return self.icon_manager.get_icon(f'video_{size}') if size == 'large' else self.icon_manager.get_icon(
|
||||
'video_small_file')
|
||||
if ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg']:
|
||||
return self.icon_manager.get_icon(f'picture_{size}')
|
||||
if ext == '.iso':
|
||||
@@ -218,13 +322,74 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.populate_files()
|
||||
|
||||
def on_window_resize(self, event):
|
||||
# This check is to prevent the resize event from firing for child widgets
|
||||
if event.widget is self:
|
||||
# Handle icon view redraw on width change, but not in search mode
|
||||
if self.view_mode.get() == "icons" and not self.search_mode:
|
||||
new_width = self.widget_manager.file_list_frame.winfo_width()
|
||||
if self.view_mode.get() == "icons" and abs(new_width - self.last_width) > 50:
|
||||
if abs(new_width - self.last_width) > 50:
|
||||
if self.resize_job:
|
||||
self.after_cancel(self.resize_job)
|
||||
self.resize_job = self.after(200, self.populate_files)
|
||||
|
||||
def repopulate_icons():
|
||||
# Ensure all pending geometry changes are processed before redrawing
|
||||
self.update_idletasks()
|
||||
self.populate_files()
|
||||
|
||||
self.resize_job = self.after(150, repopulate_icons)
|
||||
self.last_width = new_width
|
||||
|
||||
# Handle responsive buttons in the top bar
|
||||
self._handle_responsive_buttons(event.width)
|
||||
|
||||
def _handle_responsive_buttons(self, window_width):
|
||||
# This threshold might need adjustment based on your layout and button sizes
|
||||
threshold = 850
|
||||
container = self.widget_manager.responsive_buttons_container
|
||||
more_button = self.widget_manager.more_button
|
||||
|
||||
should_be_hidden = window_width < threshold
|
||||
|
||||
# Only change the layout if the state is different from the current one
|
||||
if should_be_hidden != self.responsive_buttons_hidden:
|
||||
if should_be_hidden:
|
||||
# Hide individual buttons and show the 'more' button
|
||||
container.pack_forget()
|
||||
more_button.pack(side="left", padx=5)
|
||||
else:
|
||||
# Show individual buttons and hide the 'more' button
|
||||
more_button.pack_forget()
|
||||
container.pack(side="left")
|
||||
self.responsive_buttons_hidden = should_be_hidden
|
||||
|
||||
def show_more_menu(self):
|
||||
# Create and display the dropdown menu for hidden buttons
|
||||
more_menu = tk.Menu(self, tearoff=0, background=self.style_manager.header, foreground=self.style_manager.color_foreground,
|
||||
activebackground=self.style_manager.selection_color, activeforeground=self.style_manager.color_foreground, relief='flat', borderwidth=0)
|
||||
|
||||
more_menu.add_command(label="Neuer Ordner", command=self.create_new_folder,
|
||||
image=self.icon_manager.get_icon('new_folder_small'), compound='left')
|
||||
more_menu.add_command(label="Neues Dokument", command=self.create_new_file,
|
||||
image=self.icon_manager.get_icon('new_document_small'), compound='left')
|
||||
more_menu.add_separator()
|
||||
more_menu.add_command(label="Kachelansicht", command=self.set_icon_view,
|
||||
image=self.icon_manager.get_icon('icon_view'), compound='left')
|
||||
more_menu.add_command(label="Listenansicht", command=self.set_list_view,
|
||||
image=self.icon_manager.get_icon('list_view'), compound='left')
|
||||
more_menu.add_separator()
|
||||
|
||||
# Toggle hidden files option
|
||||
hidden_files_label = "Versteckte Dateien ausblenden" if self.show_hidden_files.get() else "Versteckte Dateien anzeigen"
|
||||
hidden_files_icon = self.icon_manager.get_icon('unhide') if self.show_hidden_files.get() else self.icon_manager.get_icon('hide')
|
||||
more_menu.add_command(label=hidden_files_label, command=self.toggle_hidden_files,
|
||||
image=hidden_files_icon, compound='left')
|
||||
|
||||
# Position and show the menu
|
||||
more_button = self.widget_manager.more_button
|
||||
x = more_button.winfo_rootx()
|
||||
y = more_button.winfo_rooty() + more_button.winfo_height()
|
||||
more_menu.tk_popup(x, y)
|
||||
|
||||
def on_sidebar_resize(self, event):
|
||||
current_width = event.width
|
||||
# Define a threshold for when to hide/show text
|
||||
@@ -271,11 +436,10 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.original_path_text = self.widget_manager.path_entry.get()
|
||||
self.widget_manager.path_entry.delete(0, tk.END)
|
||||
self.widget_manager.path_entry.insert(0, "Suchbegriff eingeben...")
|
||||
self.widget_manager.path_entry.select_range(0, tk.END)
|
||||
# Set focus reliably
|
||||
self.after(50, lambda: self.widget_manager.path_entry.focus_set())
|
||||
self.widget_manager.path_entry.bind(
|
||||
"<Return>", self.execute_search)
|
||||
# Use after() to ensure the focus is set after the UI has updated
|
||||
self.after(10, lambda: self.widget_manager.path_entry.focus_set())
|
||||
self.after(20, lambda: self.widget_manager.path_entry.select_range(0, tk.END))
|
||||
|
||||
self.widget_manager.path_entry.bind(
|
||||
"<FocusIn>", self.clear_search_placeholder)
|
||||
|
||||
@@ -286,8 +450,6 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.search_mode = False
|
||||
self.widget_manager.path_entry.delete(0, tk.END)
|
||||
self.widget_manager.path_entry.insert(0, self.original_path_text)
|
||||
self.widget_manager.path_entry.bind(
|
||||
"<Return>", lambda e: self.navigate_to(self.widget_manager.path_entry.get()))
|
||||
self.widget_manager.path_entry.unbind("<FocusIn>")
|
||||
|
||||
# Hide search options
|
||||
@@ -381,11 +543,12 @@ class CustomFileDialog(tk.Toplevel):
|
||||
|
||||
# Build find command based on recursive setting (use . for current directory)
|
||||
if self.widget_manager.recursive_search.get():
|
||||
find_cmd = ['find', '.', '-iname',
|
||||
f'*{search_term}*', '-type', 'f']
|
||||
# Find both files and directories
|
||||
find_cmd = ['find', '.', '-iname', f'*{search_term}*']
|
||||
else:
|
||||
# Find both files and directories, but only in the current level
|
||||
find_cmd = ['find', '.', '-maxdepth', '1',
|
||||
'-iname', f'*{search_term}*', '-type', 'f']
|
||||
'-iname', f'*{search_term}*']
|
||||
|
||||
result = subprocess.run(
|
||||
find_cmd, capture_output=True, text=True, timeout=30)
|
||||
@@ -398,7 +561,8 @@ class CustomFileDialog(tk.Toplevel):
|
||||
if f and f.startswith('./'):
|
||||
abs_path = os.path.join(
|
||||
search_dir, f[2:]) # Remove './' prefix
|
||||
if os.path.isfile(abs_path):
|
||||
# Check if the path exists, as it might be a broken symlink or deleted
|
||||
if os.path.exists(abs_path):
|
||||
directory_files.append(abs_path)
|
||||
all_files.extend(directory_files)
|
||||
|
||||
@@ -413,11 +577,20 @@ class CustomFileDialog(tk.Toplevel):
|
||||
seen.add(file_path)
|
||||
unique_files.append(file_path)
|
||||
|
||||
# Filter based on currently selected filter pattern
|
||||
# Filter based on currently selected filter pattern and hidden file setting
|
||||
self.search_results = []
|
||||
search_hidden = self.settings.get("search_hidden_files", False)
|
||||
|
||||
for file_path in unique_files:
|
||||
# Check if path contains a hidden component (e.g., /.config/ or /some/path/to/.hidden_file)
|
||||
if not search_hidden:
|
||||
if any(part.startswith('.') for part in file_path.split(os.sep)):
|
||||
continue # Skip hidden files/files in hidden directories
|
||||
|
||||
# Check if the path exists (it might have been deleted during the search)
|
||||
if os.path.exists(file_path):
|
||||
filename = os.path.basename(file_path)
|
||||
if self._matches_filetype(filename):
|
||||
if self._matches_filetype(filename) or os.path.isdir(file_path):
|
||||
self.search_results.append(file_path)
|
||||
|
||||
# Show search results in TreeView
|
||||
@@ -494,7 +667,11 @@ class CustomFileDialog(tk.Toplevel):
|
||||
modified_time = datetime.fromtimestamp(
|
||||
stat.st_mtime).strftime('%d.%m.%Y %H:%M')
|
||||
|
||||
if os.path.isdir(file_path):
|
||||
icon = self.icon_manager.get_icon('folder_small')
|
||||
else:
|
||||
icon = self.get_file_icon(filename, 'small')
|
||||
|
||||
search_tree.insert("", "end", text=f" {filename}", image=icon,
|
||||
values=(directory, size, modified_time))
|
||||
except (FileNotFoundError, PermissionError):
|
||||
@@ -675,16 +852,17 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.all_items, error, warning = self._get_sorted_items()
|
||||
self.currently_loaded_count = 0
|
||||
|
||||
canvas = tk.Canvas(self.widget_manager.file_list_frame,
|
||||
self.icon_canvas = tk.Canvas(self.widget_manager.file_list_frame,
|
||||
highlightthickness=0, bg=self.style_manager.icon_bg_color)
|
||||
v_scrollbar = ttk.Scrollbar(
|
||||
self.widget_manager.file_list_frame, orient="vertical", command=canvas.yview)
|
||||
canvas.pack(side="left", fill="both", expand=True)
|
||||
self.widget_manager.file_list_frame, orient="vertical", command=self.icon_canvas.yview)
|
||||
self.icon_canvas.pack(side="left", fill="both", expand=True)
|
||||
v_scrollbar.pack(side="right", fill="y")
|
||||
container_frame = ttk.Frame(canvas, style="Content.TFrame")
|
||||
canvas.create_window((0, 0), window=container_frame, anchor="nw")
|
||||
container_frame.bind("<Configure>", lambda e: canvas.configure(
|
||||
scrollregion=canvas.bbox("all")))
|
||||
container_frame = ttk.Frame(self.icon_canvas, style="Content.TFrame")
|
||||
self.icon_canvas.create_window(
|
||||
(0, 0), window=container_frame, anchor="nw")
|
||||
container_frame.bind("<Configure>", lambda e: self.icon_canvas.configure(
|
||||
scrollregion=self.icon_canvas.bbox("all")))
|
||||
|
||||
def _on_mouse_wheel(event):
|
||||
if event.num == 4:
|
||||
@@ -693,12 +871,12 @@ class CustomFileDialog(tk.Toplevel):
|
||||
delta = 1
|
||||
else:
|
||||
delta = -1 * int(event.delta / 120)
|
||||
canvas.yview_scroll(delta, "units")
|
||||
self.icon_canvas.yview_scroll(delta, "units")
|
||||
# Check if scrolled to the bottom and if there are more items to load
|
||||
if self.currently_loaded_count < len(self.all_items) and canvas.yview()[1] > 0.9:
|
||||
if self.currently_loaded_count < len(self.all_items) and self.icon_canvas.yview()[1] > 0.9:
|
||||
self._load_more_items_icon_view(container_frame)
|
||||
|
||||
for widget in [canvas, container_frame]:
|
||||
for widget in [self.icon_canvas, container_frame]:
|
||||
widget.bind("<MouseWheel>", _on_mouse_wheel)
|
||||
widget.bind("<Button-4>", _on_mouse_wheel)
|
||||
widget.bind("<Button-5>", _on_mouse_wheel)
|
||||
@@ -709,23 +887,42 @@ class CustomFileDialog(tk.Toplevel):
|
||||
ttk.Label(container_frame, text=error).pack(pady=20)
|
||||
return
|
||||
|
||||
self._load_more_items_icon_view(
|
||||
widget_to_focus = self._load_more_items_icon_view(
|
||||
container_frame, item_to_rename, item_to_select)
|
||||
|
||||
if widget_to_focus:
|
||||
def scroll_to_widget():
|
||||
self.update_idletasks()
|
||||
if not widget_to_focus.winfo_exists():
|
||||
return
|
||||
y = widget_to_focus.winfo_y()
|
||||
canvas_height = self.icon_canvas.winfo_height()
|
||||
scroll_region = self.icon_canvas.bbox("all")
|
||||
if not scroll_region:
|
||||
return
|
||||
scroll_height = scroll_region[3]
|
||||
if scroll_height > canvas_height:
|
||||
fraction = y / scroll_height
|
||||
self.icon_canvas.yview_moveto(fraction)
|
||||
|
||||
self.after(100, scroll_to_widget)
|
||||
|
||||
def _load_more_items_icon_view(self, container, item_to_rename=None, item_to_select=None):
|
||||
start_index = self.currently_loaded_count
|
||||
end_index = min(len(self.all_items), start_index +
|
||||
self.items_to_load_per_batch)
|
||||
|
||||
if start_index >= end_index:
|
||||
return # All items loaded
|
||||
return None # All items loaded
|
||||
|
||||
item_width, item_height = 125, 100
|
||||
frame_width = self.widget_manager.file_list_frame.winfo_width()
|
||||
col_count = max(1, frame_width // item_width - 1)
|
||||
|
||||
row = start_index // col_count
|
||||
col = start_index % col_count
|
||||
row = start_index // col_count if col_count > 0 else 0
|
||||
col = start_index % col_count if col_count > 0 else 0
|
||||
|
||||
widget_to_focus = None
|
||||
|
||||
for i in range(start_index, end_index):
|
||||
name = self.all_items[i]
|
||||
@@ -743,6 +940,7 @@ class CustomFileDialog(tk.Toplevel):
|
||||
|
||||
if name == item_to_rename:
|
||||
self.start_rename(item_frame, path)
|
||||
widget_to_focus = item_frame
|
||||
else:
|
||||
icon = self.icon_manager.get_icon(
|
||||
'folder_large') if is_dir else self.get_file_icon(name, 'large')
|
||||
@@ -753,12 +951,7 @@ class CustomFileDialog(tk.Toplevel):
|
||||
name, 14), anchor="center", style="Item.TLabel")
|
||||
name_label.pack(fill="x", expand=True)
|
||||
|
||||
tooltip_text = name
|
||||
if is_dir and len(self.all_items) < 500:
|
||||
content_count = self._get_folder_content_count(path)
|
||||
if content_count is not None:
|
||||
tooltip_text += f"\n({content_count} Einträge)"
|
||||
Tooltip(item_frame, tooltip_text)
|
||||
Tooltip(item_frame, name)
|
||||
|
||||
for widget in [item_frame, icon_label, name_label]:
|
||||
widget.bind("<Double-Button-1>", lambda e,
|
||||
@@ -772,12 +965,17 @@ class CustomFileDialog(tk.Toplevel):
|
||||
|
||||
if name == item_to_select:
|
||||
self.on_item_select(path, item_frame)
|
||||
widget_to_focus = item_frame
|
||||
|
||||
if col_count > 0:
|
||||
col = (col + 1) % col_count
|
||||
if col == 0:
|
||||
row += 1
|
||||
else:
|
||||
row += 1
|
||||
|
||||
self.currently_loaded_count = end_index
|
||||
return widget_to_focus
|
||||
|
||||
def populate_list_view(self, item_to_rename=None, item_to_select=None):
|
||||
self.all_items, error, warning = self._get_sorted_items()
|
||||
@@ -815,7 +1013,8 @@ class CustomFileDialog(tk.Toplevel):
|
||||
def _on_scroll(*args):
|
||||
# Check if scrolled to the bottom and if there are more items to load
|
||||
if self.currently_loaded_count < len(self.all_items) and self.tree.yview()[1] > 0.9:
|
||||
self._load_more_items_list_view(item_to_rename, item_to_select)
|
||||
# On-scroll loading should not trigger rename or select.
|
||||
self._load_more_items_list_view()
|
||||
v_scrollbar.set(*args)
|
||||
self.tree.configure(yscrollcommand=_on_scroll)
|
||||
|
||||
@@ -886,7 +1085,7 @@ class CustomFileDialog(tk.Toplevel):
|
||||
child.state(['selected'])
|
||||
self.selected_item_frame = item_frame
|
||||
self.selected_file = path
|
||||
self.update_status_bar()
|
||||
self.update_status_bar(path) # Pass selected path
|
||||
self.bind("<F2>", lambda e, p=path,
|
||||
f=item_frame: self.on_rename_request(e, p, f))
|
||||
if self.dialog_mode == "save" and not os.path.isdir(path):
|
||||
@@ -899,8 +1098,9 @@ class CustomFileDialog(tk.Toplevel):
|
||||
return
|
||||
item_id = self.tree.selection()[0]
|
||||
item_text = self.tree.item(item_id, 'text').strip()
|
||||
self.selected_file = os.path.join(self.current_dir, item_text)
|
||||
self.update_status_bar()
|
||||
path = os.path.join(self.current_dir, item_text)
|
||||
self.selected_file = path
|
||||
self.update_status_bar(path) # Pass selected path
|
||||
if self.dialog_mode == "save" and not os.path.isdir(self.selected_file):
|
||||
self.widget_manager.filename_entry.delete(0, tk.END)
|
||||
self.widget_manager.filename_entry.insert(0, item_text)
|
||||
@@ -1006,13 +1206,19 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.update_status_bar()
|
||||
self.update_action_buttons_state()
|
||||
|
||||
def go_up_level(self):
|
||||
"""Navigates one directory level up."""
|
||||
new_path = os.path.dirname(self.current_dir)
|
||||
if new_path != self.current_dir: # Avoid getting stuck at the root
|
||||
self.navigate_to(new_path)
|
||||
|
||||
def update_nav_buttons(self):
|
||||
self.widget_manager.back_button.config(
|
||||
state=tk.NORMAL if self.history_pos > 0 else tk.DISABLED)
|
||||
self.widget_manager.forward_button.config(state=tk.NORMAL if self.history_pos < len(
|
||||
self.history) - 1 else tk.DISABLED)
|
||||
|
||||
def update_status_bar(self):
|
||||
def update_status_bar(self, selected_path=None):
|
||||
try:
|
||||
total, used, free = shutil.disk_usage(self.current_dir)
|
||||
free_str = self._format_size(free)
|
||||
@@ -1021,10 +1227,19 @@ class CustomFileDialog(tk.Toplevel):
|
||||
self.widget_manager.storage_bar['value'] = (used / total) * 100
|
||||
|
||||
status_text = ""
|
||||
if self.dialog_mode == "open" and self.selected_file and os.path.exists(self.selected_file) and not os.path.isdir(self.selected_file):
|
||||
size = os.path.getsize(self.selected_file)
|
||||
if selected_path and os.path.exists(selected_path):
|
||||
if os.path.isdir(selected_path):
|
||||
# Display item count for directories
|
||||
content_count = self._get_folder_content_count(selected_path)
|
||||
if content_count is not None:
|
||||
status_text = f"'{os.path.basename(selected_path)}' ({content_count} Einträge)"
|
||||
else:
|
||||
status_text = f"'{os.path.basename(selected_path)}'"
|
||||
else:
|
||||
# Display size for files
|
||||
size = os.path.getsize(selected_path)
|
||||
size_str = self._format_size(size)
|
||||
status_text = f"'{os.path.basename(self.selected_file)}' Größe: {size_str}"
|
||||
status_text = f"'{os.path.basename(selected_path)}' Größe: {size_str}"
|
||||
self.widget_manager.status_bar.config(text=status_text)
|
||||
except FileNotFoundError:
|
||||
self.widget_manager.status_bar.config(
|
||||
@@ -1050,6 +1265,48 @@ class CustomFileDialog(tk.Toplevel):
|
||||
def get_selected_file(self):
|
||||
return self.selected_file
|
||||
|
||||
def delete_selected_item(self, event=None):
|
||||
"""Deletes or moves the selected item to trash based on settings."""
|
||||
if not self.selected_file or not os.path.exists(self.selected_file):
|
||||
return
|
||||
|
||||
use_trash = self.settings.get("use_trash", False) and SEND2TRASH_AVAILABLE
|
||||
confirm = self.settings.get("confirm_delete", False)
|
||||
|
||||
action_text = "in den Papierkorb verschieben" if use_trash else "endgültig löschen"
|
||||
item_name = os.path.basename(self.selected_file)
|
||||
|
||||
if not confirm:
|
||||
dialog = MessageDialog(
|
||||
master=self,
|
||||
title="Bestätigung erforderlich",
|
||||
text=f"Möchten Sie '{item_name}' wirklich {action_text}?",
|
||||
message_type="question"
|
||||
)
|
||||
if not dialog.show():
|
||||
return
|
||||
|
||||
try:
|
||||
if use_trash:
|
||||
send2trash.send2trash(self.selected_file)
|
||||
else:
|
||||
if os.path.isdir(self.selected_file):
|
||||
shutil.rmtree(self.selected_file)
|
||||
else:
|
||||
os.remove(self.selected_file)
|
||||
|
||||
self.populate_files()
|
||||
self.widget_manager.status_bar.config(
|
||||
text=f"'{item_name}' wurde erfolgreich entfernt.")
|
||||
|
||||
except Exception as e:
|
||||
MessageDialog(
|
||||
master=self,
|
||||
title="Fehler",
|
||||
text=f"Fehler beim Entfernen von '{item_name}':\n{e}",
|
||||
message_type="error"
|
||||
).show()
|
||||
|
||||
def create_new_folder(self):
|
||||
self._create_new_item(is_folder=True)
|
||||
|
||||
@@ -1164,7 +1421,19 @@ class CustomFileDialog(tk.Toplevel):
|
||||
entry.bind("<Escape>", cancel_rename)
|
||||
|
||||
def _start_rename_list_view(self, item_id):
|
||||
x, y, width, height = self.tree.bbox(item_id, column="#0")
|
||||
# First, ensure the item is visible by scrolling to it.
|
||||
self.tree.see(item_id)
|
||||
# Force the UI to process the scrolling and other pending events.
|
||||
self.tree.update_idletasks()
|
||||
|
||||
# Now, get the bounding box. It should be available since the item is visible.
|
||||
bbox = self.tree.bbox(item_id, column="#0")
|
||||
|
||||
# If bbox is still empty (e.g., view is not focused), abort to prevent crash.
|
||||
if not bbox:
|
||||
return
|
||||
|
||||
x, y, width, height = bbox
|
||||
entry = ttk.Entry(self.tree)
|
||||
# Set a fixed width for the entry widget to prevent it from expanding too much
|
||||
entry_width = self.tree.column("#0", "width")
|
||||
|
@@ -33,7 +33,7 @@ class GlotzMol(tk.Tk):
|
||||
dialog = CustomFileDialog(self,
|
||||
initial_dir=os.path.expanduser("~"),
|
||||
filetypes=[("All Files", "*.*")
|
||||
], dialog_mode="save")
|
||||
])
|
||||
|
||||
# This is the crucial part: wait for the dialog to be closed
|
||||
self.wait_window(dialog)
|
||||
@@ -55,7 +55,7 @@ if __name__ == "__main__":
|
||||
style = ttk.Style(root)
|
||||
root.tk.call('source', f"{theme_path}/water.tcl")
|
||||
try:
|
||||
root.tk.call('set_theme', 'light')
|
||||
root.tk.call('set_theme', 'dark')
|
||||
except tk.TclError:
|
||||
pass
|
||||
root.mainloop()
|
||||
|
Reference in New Issue
Block a user