commit 47

This commit is contained in:
2025-08-03 14:46:55 +02:00
parent 94c32ddd9e
commit 2880e0d7a1
5 changed files with 93 additions and 114 deletions

View File

@@ -1,9 +1,8 @@
#!/usr/bin/python3
"""App configuration for Wire-Py"""
import logging
"""App configuration for Custom File Dialog"""
from pathlib import Path
import os
from subprocess import CompletedProcess, run
from typing import Dict, Any
from shared_libs.common_tools import Translate
@@ -19,7 +18,7 @@ class AppConfig:
Key Responsibilities:
- Centralizes all configuration values (paths, UI preferences, localization).
- Ensures required directories and files exist on startup.
- Ensures required directories and files exist.
- Handles translation setup via `gettext` for multilingual support.
- Manages default settings file generation.
- Configures autostart services using systemd for user-specific launch behavior.
@@ -32,11 +31,6 @@ class AppConfig:
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
MAX_ITEMS_TO_DISPLAY = 1000
# Logging
LOG_DIR = Path.home() / ".local/share/lxlogs"
Path(LOG_DIR).mkdir(parents=True, exist_ok=True)
LOG_FILE_PATH = LOG_DIR / "cfiledialog.log"
# Base paths
BASE_DIR: Path = Path.home()
CONFIG_DIR: Path = BASE_DIR / ".config/cfiledialog"
@@ -48,29 +42,17 @@ class AppConfig:
"# Theme": "dark",
"# Tooltips": True,
"# Autostart": "off",
"# Logfile": LOG_FILE_PATH,
}
# Updates
# 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year
VERSION: str = "v. 1.07.2725"
UPDATE_URL: str = "https://git.ilunix.de/api/v1/repos/punix/example/releases"
DOWNLOAD_URL: str = "https://git.ilunix.de/punix/example/archive"
# UI configuration
UI_CONFIG: Dict[str, Any] = {
"window_title": "File Dialog",
"window_size": (1050, 850),
"window_min_size": (750, 550),
"font_family": "Ubuntu",
"font_size": 11,
"resizable_window": (True, True),
}
# System-dependent paths
SYSTEM_PATHS: Dict[str, Path] = {
"tcl_path": "/usr/share/TK-Themes",
}
@classmethod
def ensure_directories(cls) -> None:
"""Ensures that all required directories exist"""
@@ -86,37 +68,12 @@ class AppConfig:
)
cls.SETTINGS_FILE.write_text(content)
@classmethod
def ensure_log(cls) -> None:
"""Ensures that the log file exists"""
if not cls.LOG_FILE_PATH.exists():
cls.LOG_FILE_PATH.touch()
# here is initializing the class for translation strings
_ = Translate.setup_translations("custom_file_fialog")
class Msg:
"""
A utility class that provides centralized access to translated message strings.
This class contains a dictionary of message strings used throughout the custom file dialog application.
All strings are prepared for translation using gettext. The short key names make the code
more concise while maintaining readability.
Attributes:
STR (dict): A dictionary mapping short keys to translated message strings.
Keys are abbreviated for brevity but remain descriptive.
Usage:
Import this class and access messages using the dictionary:
`Msg.STR["sel_tl"]` returns the translated "Select tunnel" message.
Note:
Ensure that gettext translation is properly initialized before
accessing these strings to ensure correct localization.
"""
STR: Dict[str, str] = {
# Strings for messages

View File

@@ -6,18 +6,13 @@ from datetime import datetime
import subprocess
import json
from shared_libs.message import MessageDialog
from shared_libs.common_tools import IconManager, Tooltip, ConfigManager, LxTools, ThemeManager
from shared_libs.common_tools import IconManager, Tooltip, ConfigManager, LxTools
from cfd_app_config import AppConfig
from cfd_ui_setup import StyleManager, WidgetManager, get_xdg_user_dir
# Helper to make icon paths robust, so the script can be run from anywhere
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
MAX_ITEMS_TO_DISPLAY = 1000
class CustomFileDialog(tk.Toplevel):
def __init__(self, parent, initial_dir=None, filetypes=None, dialog_mode="open"):
def __init__(self, parent, initial_dir=None, filetypes=None, dialog_mode="open", title="File Dialog"):
super().__init__(parent)
self.my_tool_tip = None
@@ -27,15 +22,10 @@ class CustomFileDialog(tk.Toplevel):
self.y_height = AppConfig.UI_CONFIG["window_size"][1]
# Set the window size
self.geometry(f"{self.x_width}x{self.y_height}")
self.minsize(AppConfig.UI_CONFIG["window_size"][0],
AppConfig.UI_CONFIG["window_size"][1],
self.minsize(AppConfig.UI_CONFIG["window_min_size"][0],
AppConfig.UI_CONFIG["window_min_size"][1],
)
self.title(AppConfig.UI_CONFIG["window_title"])
# self.tk.call(
# "source", f"{AppConfig.SYSTEM_PATHS['tcl_path']}/water.tcl")
# ConfigManager.init(AppConfig.SETTINGS_FILE)
# theme = ConfigManager.get("theme")
# ThemeManager.change_theme(self, theme)
self.title(title)
self.image = IconManager()
LxTools.center_window_cross_platform(self, self.x_width, self.y_height)
self.parent = parent
@@ -67,7 +57,7 @@ class CustomFileDialog(tk.Toplevel):
def get_file_icon(self, filename, size='large'):
ext = os.path.splitext(filename)[1].lower()
if ext == '.py':
return self.icon_manager.get_icon(f'python_{size}')
if ext == '.pdf':
@@ -381,14 +371,16 @@ class CustomFileDialog(tk.Toplevel):
filename = item['text'].strip()
directory = item['values'][0]
full_path = os.path.join(directory, filename)
# Update status bar
try:
stat = os.stat(full_path)
size_str = self._format_size(stat.st_size)
self.widget_manager.status_bar.config(text=f"'{filename}' Größe: {size_str}")
self.widget_manager.status_bar.config(
text=f"'{filename}' Größe: {size_str}")
except (FileNotFoundError, PermissionError):
self.widget_manager.status_bar.config(text=f"'{filename}' nicht zugänglich")
self.widget_manager.status_bar.config(
text=f"'{filename}' nicht zugänglich")
# If in save mode, update filename entry
if self.dialog_mode == "save":
@@ -437,9 +429,9 @@ class CustomFileDialog(tk.Toplevel):
directory = item['values'][0]
# Exit search mode and navigate to the directory
self.toggle_search_mode() # To restore normal view
self.toggle_search_mode() # To restore normal view
self.navigate_to(directory)
# Select the file in the list
self.after(100, lambda: self._select_file_in_view(filename))
@@ -451,7 +443,7 @@ class CustomFileDialog(tk.Toplevel):
self.tree.focus(item_id)
self.tree.see(item_id)
break
else: # icon view
else: # icon view
# This is more complex as items are in a grid. A simple selection is not straightforward.
# For now, we just navigate to the folder.
pass
@@ -499,13 +491,13 @@ class CustomFileDialog(tk.Toplevel):
try:
if not os.path.isdir(folder_path) or not os.access(folder_path, os.R_OK):
return None
items = os.listdir(folder_path)
# Filter based on hidden files setting
if not self.show_hidden_files.get():
items = [item for item in items if not item.startswith('.')]
return len(items)
except (PermissionError, FileNotFoundError):
return None
@@ -558,9 +550,12 @@ class CustomFileDialog(tk.Toplevel):
scrollregion=canvas.bbox("all")))
def _on_mouse_wheel(event):
if event.num == 4: delta = -1
elif event.num == 5: delta = 1
else: delta = -1 * int(event.delta / 120)
if event.num == 4:
delta = -1
elif event.num == 5:
delta = 1
else:
delta = -1 * int(event.delta / 120)
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:
@@ -577,18 +572,21 @@ class CustomFileDialog(tk.Toplevel):
ttk.Label(container_frame, text=error).pack(pady=20)
return
self._load_more_items_icon_view(container_frame, item_to_rename, item_to_select)
self._load_more_items_icon_view(
container_frame, item_to_rename, item_to_select)
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)
end_index = min(len(self.all_items), start_index +
self.items_to_load_per_batch)
if start_index >= end_index: return # All items loaded
if start_index >= end_index:
return # 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
@@ -609,12 +607,15 @@ class CustomFileDialog(tk.Toplevel):
if name == item_to_rename:
self.start_rename(item_frame, path)
else:
icon = self.icon_manager.get_icon('folder_large') if is_dir else self.get_file_icon(name, 'large')
icon_label = ttk.Label(item_frame, image=icon, style="Icon.TLabel")
icon = self.icon_manager.get_icon(
'folder_large') if is_dir else self.get_file_icon(name, 'large')
icon_label = ttk.Label(
item_frame, image=icon, style="Icon.TLabel")
icon_label.pack(pady=(10, 5))
name_label = ttk.Label(item_frame, text=self.shorten_text(name, 14), anchor="center", style="Item.TLabel")
name_label = ttk.Label(item_frame, text=self.shorten_text(
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)
@@ -623,17 +624,22 @@ class CustomFileDialog(tk.Toplevel):
Tooltip(item_frame, tooltip_text)
for widget in [item_frame, icon_label, name_label]:
widget.bind("<Double-Button-1>", lambda e, p=path: self.on_item_double_click(p))
widget.bind("<Button-1>", lambda e, p=path, f=item_frame: self.on_item_select(p, f))
widget.bind("<ButtonRelease-3>", lambda e, p=path: self._show_context_menu(e, p))
widget.bind("<F2>", lambda e, p=path, f=item_frame: self.on_rename_request(e, p, f))
widget.bind("<Double-Button-1>", lambda e,
p=path: self.on_item_double_click(p))
widget.bind("<Button-1>", lambda e, p=path,
f=item_frame: self.on_item_select(p, f))
widget.bind("<ButtonRelease-3>", lambda e,
p=path: self._show_context_menu(e, p))
widget.bind("<F2>", lambda e, p=path,
f=item_frame: self.on_rename_request(e, p, f))
if name == item_to_select:
self.on_item_select(path, item_frame)
col = (col + 1) % col_count
if col == 0: row += 1
if col == 0:
row += 1
self.currently_loaded_count = end_index
def populate_list_view(self, item_to_rename=None, item_to_select=None):
@@ -646,7 +652,8 @@ class CustomFileDialog(tk.Toplevel):
tree_frame.grid_columnconfigure(0, weight=1)
columns = ("size", "type", "modified")
self.tree = ttk.Treeview(tree_frame, columns=columns, show="tree headings")
self.tree = ttk.Treeview(
tree_frame, columns=columns, show="tree headings")
self.tree.heading("#0", text="Name", anchor="w")
self.tree.column("#0", anchor="w", width=250, stretch=True)
@@ -657,9 +664,12 @@ class CustomFileDialog(tk.Toplevel):
self.tree.heading("modified", text="Geändert am", anchor="w")
self.tree.column("modified", anchor="w", width=160, stretch=False)
v_scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
h_scrollbar = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
v_scrollbar = ttk.Scrollbar(
tree_frame, orient="vertical", command=self.tree.yview)
h_scrollbar = ttk.Scrollbar(
tree_frame, orient="horizontal", command=self.tree.xview)
self.tree.configure(yscrollcommand=v_scrollbar.set,
xscrollcommand=h_scrollbar.set)
self.tree.grid(row=0, column=0, sticky='nsew')
v_scrollbar.grid(row=0, column=1, sticky='ns')
@@ -687,10 +697,11 @@ class CustomFileDialog(tk.Toplevel):
def _load_more_items_list_view(self, 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)
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 # All items loaded
for i in range(start_index, end_index):
name = self.all_items[i]
@@ -702,12 +713,16 @@ class CustomFileDialog(tk.Toplevel):
continue
try:
stat = os.stat(path)
modified_time = datetime.fromtimestamp(stat.st_mtime).strftime('%d.%m.%Y %H:%M')
modified_time = datetime.fromtimestamp(
stat.st_mtime).strftime('%d.%m.%Y %H:%M')
if is_dir:
icon, file_type, size = self.icon_manager.get_icon('folder_small'), "Ordner", ""
icon, file_type, size = self.icon_manager.get_icon(
'folder_small'), "Ordner", ""
else:
icon, file_type, size = self.get_file_icon(name, 'small'), "Datei", self._format_size(stat.st_size)
item_id = self.tree.insert("", "end", text=f" {name}", image=icon, values=(size, file_type, modified_time))
icon, file_type, size = self.get_file_icon(
name, 'small'), "Datei", self._format_size(stat.st_size)
item_id = self.tree.insert("", "end", text=f" {name}", image=icon, values=(
size, file_type, modified_time))
if name == item_to_rename:
self.tree.selection_set(item_id)
self.tree.focus(item_id)
@@ -719,7 +734,7 @@ class CustomFileDialog(tk.Toplevel):
self.tree.see(item_id)
except (FileNotFoundError, PermissionError):
continue
self.currently_loaded_count = end_index
def on_item_select(self, path, item_frame):
@@ -735,10 +750,12 @@ class CustomFileDialog(tk.Toplevel):
self.selected_item_frame = item_frame
self.selected_file = path
self.update_status_bar()
self.bind("<F2>", lambda e, p=path, f=item_frame: self.on_rename_request(e, p, f))
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):
self.widget_manager.filename_entry.delete(0, tk.END)
self.widget_manager.filename_entry.insert(0, os.path.basename(path))
self.widget_manager.filename_entry.insert(
0, os.path.basename(path))
def on_list_select(self, event):
if not self.tree.selection():
@@ -773,8 +790,6 @@ class CustomFileDialog(tk.Toplevel):
if item_path and item_frame:
self.start_rename(item_frame, item_path)
def on_item_double_click(self, path):
if os.path.isdir(path):
self.navigate_to(path)
@@ -783,7 +798,8 @@ class CustomFileDialog(tk.Toplevel):
self.destroy()
elif self.dialog_mode == "save":
self.widget_manager.filename_entry.delete(0, tk.END)
self.widget_manager.filename_entry.insert(0, os.path.basename(path))
self.widget_manager.filename_entry.insert(
0, os.path.basename(path))
self.on_save()
def on_list_double_click(self, event):
@@ -930,7 +946,8 @@ class CustomFileDialog(tk.Toplevel):
def _copy_to_clipboard(self, data):
self.clipboard_clear()
self.clipboard_append(data)
self.widget_manager.status_bar.config(text=f"'{self.shorten_text(data, 50)}' in Zwischenablage kopiert.")
self.widget_manager.status_bar.config(
text=f"'{self.shorten_text(data, 50)}' in Zwischenablage kopiert.")
def _show_context_menu(self, event, item_path):
if not item_path:
@@ -940,14 +957,18 @@ class CustomFileDialog(tk.Toplevel):
if hasattr(self, 'context_menu') and self.context_menu.winfo_exists():
self.context_menu.destroy()
self.context_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)
self.context_menu.add_command(label="Dateiname in Zwischenablage", command=lambda: self._copy_to_clipboard(os.path.basename(item_path)))
self.context_menu.add_command(label="Pfad in Zwischenablage", command=lambda: self._copy_to_clipboard(item_path))
self.context_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)
self.context_menu.add_command(label="Dateiname in Zwischenablage",
command=lambda: self._copy_to_clipboard(os.path.basename(item_path)))
self.context_menu.add_command(
label="Pfad in Zwischenablage", command=lambda: self._copy_to_clipboard(item_path))
if self.search_mode:
self.context_menu.add_separator()
self.context_menu.add_command(label="Speicherort öffnen", command=lambda: self._open_file_location_from_context(item_path))
self.context_menu.add_command(
label="Speicherort öffnen", command=lambda: self._open_file_location_from_context(item_path))
self.context_menu.tk_popup(event.x_root, event.y_root)
return "break"
@@ -985,7 +1006,8 @@ class CustomFileDialog(tk.Toplevel):
if os.path.exists(new_path):
self.widget_manager.status_bar.config(
text=f"'{new_name}' existiert bereits.")
self.populate_files(item_to_select=os.path.basename(item_path))
self.populate_files(
item_to_select=os.path.basename(item_path))
return
try:
os.rename(item_path, new_path)

View File

@@ -33,7 +33,7 @@ class GlotzMol(tk.Tk):
dialog = CustomFileDialog(self,
initial_dir=os.path.expanduser("~"),
filetypes=[("All Files", "*.*")
])
], dialog_mode="save", title="Save File")
# 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', 'dark')
root.tk.call('set_theme', 'light')
except tk.TclError:
pass
root.mainloop()