commit 47
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user