351 lines
14 KiB
Python
351 lines
14 KiB
Python
import tkinter as tk
|
|
from tkinter import ttk, filedialog, messagebox, simpledialog
|
|
import os
|
|
import webbrowser
|
|
from typing import Callable, Optional, Dict, Any, List
|
|
from models.program import Program
|
|
from config import Config
|
|
from utils.icon_utils import IconManager
|
|
from gui.modern_widgets import ModernStyle, ModernCard, ModernButton
|
|
|
|
|
|
class BaseDialog:
|
|
def __init__(self, parent: tk.Widget, title: str, width: int = 500, height: int = 400):
|
|
self.parent = parent
|
|
self.result = None
|
|
self.modern_style = ModernStyle()
|
|
self.icon_manager = IconManager()
|
|
theme = Config.get_theme()
|
|
|
|
self.window = tk.Toplevel(parent)
|
|
self.window.title(title)
|
|
self.window.geometry(f"{width}x{height}")
|
|
self.window.minsize(width, height) # Mindestgröße setzen
|
|
self.window.resizable(True, True) # Größe änderbar machen
|
|
self.window.grab_set()
|
|
self.window.configure(bg=theme["bg_primary"])
|
|
|
|
# Set icon
|
|
try:
|
|
self.window.iconbitmap(self.icon_manager.resource_path(Config.ICON_FILE))
|
|
except:
|
|
pass
|
|
|
|
# Configure modern styling
|
|
style = ttk.Style()
|
|
self.modern_style.configure_styles(style)
|
|
|
|
self._center_window()
|
|
self._create_widgets()
|
|
|
|
def _center_window(self):
|
|
self.window.update_idletasks()
|
|
width = self.window.winfo_width()
|
|
height = self.window.winfo_height()
|
|
x = (self.window.winfo_screenwidth() // 2) - (width // 2)
|
|
y = (self.window.winfo_screenheight() // 2) - (height // 2)
|
|
self.window.geometry(f"{width}x{height}+{x}+{y}")
|
|
|
|
def _create_widgets(self):
|
|
pass
|
|
|
|
def show(self):
|
|
self.window.wait_window()
|
|
return self.result
|
|
|
|
|
|
class ProgramDialog(BaseDialog):
|
|
def __init__(self, parent: tk.Widget, title: str, categories: List[str],
|
|
program: Optional[Program] = None):
|
|
self.categories = categories
|
|
self.program = program
|
|
self.is_edit = program is not None
|
|
super().__init__(parent, title, 450, 280)
|
|
|
|
def _create_widgets(self):
|
|
# Simple container
|
|
container = ttk.Frame(self.window, padding="20")
|
|
container.pack(fill=tk.BOTH, expand=True)
|
|
|
|
# Form fields using grid
|
|
self._create_form_fields(container)
|
|
|
|
# Buttons
|
|
self._create_buttons(container)
|
|
|
|
def _create_form_fields(self, parent):
|
|
# Name field
|
|
ttk.Label(parent, text="Programmname:").grid(row=0, column=0, sticky="w", pady=5)
|
|
self.name_entry = ttk.Entry(parent, width=30)
|
|
self.name_entry.grid(row=0, column=1, columnspan=2, sticky="we", pady=5)
|
|
|
|
# Path field
|
|
ttk.Label(parent, text="Programmpfad:").grid(row=1, column=0, sticky="w", pady=5)
|
|
self.path_entry = ttk.Entry(parent, width=30)
|
|
self.path_entry.grid(row=1, column=1, sticky="we", pady=5)
|
|
|
|
browse_button = ttk.Button(parent, text="Durchsuchen", command=self._browse_file)
|
|
browse_button.grid(row=1, column=2, padx=5, pady=5)
|
|
|
|
# Category field
|
|
ttk.Label(parent, text="Kategorie:").grid(row=2, column=0, sticky="w", pady=5)
|
|
self.category_combobox = ttk.Combobox(parent, width=28, values=sorted(self.categories))
|
|
self.category_combobox.grid(row=2, column=1, columnspan=2, sticky="we", pady=5)
|
|
|
|
# Admin rights checkbox
|
|
self.admin_var = tk.BooleanVar(value=False)
|
|
admin_check = ttk.Checkbutton(parent, text="Benötigt Administratorrechte",
|
|
variable=self.admin_var)
|
|
admin_check.grid(row=3, column=0, columnspan=3, sticky="w", pady=5)
|
|
|
|
# Fill fields if editing
|
|
if self.program:
|
|
self.name_entry.insert(0, self.program.name)
|
|
self.path_entry.insert(0, self.program.path)
|
|
if self.program.category:
|
|
self.category_combobox.set(self.program.category)
|
|
elif self.categories:
|
|
self.category_combobox.current(0)
|
|
self.admin_var.set(self.program.requires_admin)
|
|
elif self.categories:
|
|
self.category_combobox.current(0)
|
|
|
|
self.name_entry.focus_set()
|
|
|
|
# Make column 1 expandable
|
|
parent.columnconfigure(1, weight=1)
|
|
|
|
def _create_buttons(self, parent):
|
|
# Button frame
|
|
button_frame = ttk.Frame(parent)
|
|
button_frame.grid(row=4, column=0, columnspan=3, pady=15)
|
|
|
|
ttk.Button(button_frame, text="Speichern", command=self._save).pack(side=tk.LEFT, padx=5)
|
|
ttk.Button(button_frame, text="Abbrechen", command=self.window.destroy).pack(side=tk.LEFT, padx=5)
|
|
|
|
def _browse_file(self):
|
|
if not self.is_edit:
|
|
self.path_entry.delete(0, tk.END)
|
|
filename = filedialog.askopenfilename(
|
|
filetypes=[
|
|
("Ausführbare Dateien", "*.exe"),
|
|
("Batch-Dateien", "*.bat;*.cmd"),
|
|
("Alle ausführbaren Dateien", "*.exe;*.bat;*.cmd"),
|
|
("Alle Dateien", "*.*")
|
|
]
|
|
)
|
|
if filename:
|
|
if not self.is_edit:
|
|
self.path_entry.insert(0, filename)
|
|
else:
|
|
self.path_entry.delete(0, tk.END)
|
|
self.path_entry.insert(0, filename)
|
|
|
|
def _save(self):
|
|
name = self.name_entry.get().strip()
|
|
path = self.path_entry.get().strip()
|
|
category = self.category_combobox.get().strip()
|
|
requires_admin = self.admin_var.get()
|
|
|
|
if not name or not path:
|
|
messagebox.showwarning("Warnung", "Bitte füllen Sie alle Felder aus.")
|
|
return
|
|
|
|
if not os.path.exists(path):
|
|
messagebox.showwarning("Warnung", "Der angegebene Pfad existiert nicht.")
|
|
return
|
|
|
|
self.result = Program(
|
|
name=name,
|
|
path=path,
|
|
category=category if category else None,
|
|
requires_admin=requires_admin
|
|
)
|
|
self.window.destroy()
|
|
|
|
|
|
class OptionsDialog(BaseDialog):
|
|
def __init__(self, parent: tk.Widget, current_options: Dict[str, Any]):
|
|
self.current_options = current_options.copy()
|
|
super().__init__(parent, "Programmoptionen", 400, 320)
|
|
|
|
def _create_widgets(self):
|
|
# Simple container with normal padding
|
|
container = ttk.Frame(self.window, padding="20")
|
|
container.pack(fill=tk.BOTH, expand=True)
|
|
|
|
# Title
|
|
title_label = ttk.Label(container, text="Programmoptionen",
|
|
font=("Segoe UI", 12, "bold"))
|
|
title_label.grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 15))
|
|
|
|
# Sort option
|
|
ttk.Label(container, text="Standardsortierung:").grid(row=1, column=0, sticky="w", pady=5)
|
|
self.sort_var = tk.StringVar(value=self._get_sort_option_text())
|
|
sort_combobox = ttk.Combobox(container, textvariable=self.sort_var,
|
|
values=Config.SORT_OPTIONS,
|
|
width=20)
|
|
sort_combobox.grid(row=1, column=1, sticky="we", pady=5)
|
|
sort_combobox.configure(state="readonly")
|
|
|
|
# Remember category option
|
|
self.remember_category_var = tk.BooleanVar(
|
|
value=self.current_options.get("remember_last_category", False)
|
|
)
|
|
remember_category_check = ttk.Checkbutton(
|
|
container,
|
|
text="Letzte ausgewählte Kategorie merken",
|
|
variable=self.remember_category_var
|
|
)
|
|
remember_category_check.grid(row=2, column=0, columnspan=2, sticky="w", pady=10)
|
|
|
|
# Separator
|
|
separator = ttk.Separator(container, orient="horizontal")
|
|
separator.grid(row=3, column=0, columnspan=2, sticky="ew", pady=10)
|
|
|
|
# Info section
|
|
info_frame = ttk.Frame(container)
|
|
info_frame.grid(row=4, column=0, columnspan=2, sticky="ew", pady=5)
|
|
|
|
ttk.Label(info_frame, text=f"Version: {Config.APP_VERSION}",
|
|
font=("Segoe UI", 9)).pack(anchor="w")
|
|
ttk.Label(info_frame, text=f"{Config.APP_AUTHOR}",
|
|
font=("Segoe UI", 9)).pack(anchor="w")
|
|
ttk.Label(info_frame, text=f"{Config.APP_DESCRIPTION}",
|
|
font=("Segoe UI", 9)).pack(anchor="w")
|
|
|
|
# Link
|
|
link_label = ttk.Label(info_frame, text="Quellcode auf git.ponywave.de",
|
|
font=("Segoe UI", 9, "underline"),
|
|
foreground="blue", cursor="hand2")
|
|
link_label.pack(anchor="w", pady=(5, 0))
|
|
link_label.bind("<Button-1>", lambda e: self._open_url(Config.SOURCE_URL))
|
|
|
|
# Buttons
|
|
button_frame = ttk.Frame(container)
|
|
button_frame.grid(row=5, column=0, columnspan=2, pady=20)
|
|
|
|
ttk.Button(button_frame, text="Speichern", command=self._save).pack(side=tk.LEFT, padx=5)
|
|
ttk.Button(button_frame, text="Abbrechen", command=self.window.destroy).pack(side=tk.LEFT, padx=5)
|
|
|
|
# Make column 1 expandable
|
|
container.columnconfigure(1, weight=1)
|
|
|
|
|
|
def _get_sort_option_text(self) -> str:
|
|
column = self.current_options.get("default_sort_column")
|
|
reverse = self.current_options.get("default_sort_reverse", False)
|
|
|
|
if column == "name":
|
|
return "Programm (Z-A)" if reverse else "Programm (A-Z)"
|
|
elif column == "category":
|
|
return "Kategorie (Z-A)" if reverse else "Kategorie (A-Z)"
|
|
else:
|
|
return "Keine"
|
|
|
|
def _save(self):
|
|
sort_option = self.sort_var.get()
|
|
remember_category = self.remember_category_var.get()
|
|
|
|
# Translate sort options
|
|
if sort_option == "Programm (A-Z)":
|
|
sort_column, sort_reverse = "name", False
|
|
elif sort_option == "Programm (Z-A)":
|
|
sort_column, sort_reverse = "name", True
|
|
elif sort_option == "Kategorie (A-Z)":
|
|
sort_column, sort_reverse = "category", False
|
|
elif sort_option == "Kategorie (Z-A)":
|
|
sort_column, sort_reverse = "category", True
|
|
else: # "Keine"
|
|
sort_column, sort_reverse = None, False
|
|
|
|
self.result = {
|
|
"default_sort_column": sort_column,
|
|
"default_sort_reverse": sort_reverse,
|
|
"remember_last_category": remember_category
|
|
}
|
|
self.window.destroy()
|
|
|
|
def _open_url(self, url: str):
|
|
try:
|
|
webbrowser.open(url)
|
|
except Exception as e:
|
|
messagebox.showerror("Fehler", f"Fehler beim Öffnen der URL: {str(e)}")
|
|
|
|
|
|
class CategoryDialog(BaseDialog):
|
|
def __init__(self, parent: tk.Widget, dialog_type: str = "add", categories: List[str] = None):
|
|
self.dialog_type = dialog_type
|
|
self.categories = categories or []
|
|
|
|
if dialog_type == "add":
|
|
title = "Neue Kategorie"
|
|
width, height = 400, 150
|
|
else: # delete
|
|
title = "Kategorie löschen"
|
|
width, height = 450, 200
|
|
|
|
super().__init__(parent, title, width, height)
|
|
|
|
def _create_widgets(self):
|
|
container = ttk.Frame(self.window, padding="20")
|
|
container.pack(fill=tk.BOTH, expand=True)
|
|
|
|
if self.dialog_type == "add":
|
|
# Label
|
|
ttk.Label(container, text="Geben Sie den Namen der neuen Kategorie ein:").pack(pady=(0, 10))
|
|
|
|
# Entry
|
|
self.name_entry = ttk.Entry(container, width=30)
|
|
self.name_entry.pack(pady=(0, 15))
|
|
self.name_entry.focus_set()
|
|
|
|
# Bind Enter key
|
|
self.name_entry.bind("<Return>", lambda e: self._ok())
|
|
else: # delete
|
|
# Label
|
|
label_text = f"Verfügbare Kategorien: {', '.join(sorted(self.categories))}\n\nWelche Kategorie soll gelöscht werden?"
|
|
ttk.Label(container, text=label_text, wraplength=400).pack(pady=(0, 10))
|
|
|
|
# Combobox for category selection
|
|
self.category_combobox = ttk.Combobox(container, values=sorted(self.categories), width=30)
|
|
self.category_combobox.pack(pady=(0, 15))
|
|
self.category_combobox.focus_set()
|
|
if self.categories:
|
|
self.category_combobox.current(0)
|
|
|
|
# Buttons
|
|
button_frame = ttk.Frame(container)
|
|
button_frame.pack()
|
|
|
|
ttk.Button(button_frame, text="OK", command=self._ok).pack(side=tk.LEFT, padx=5)
|
|
ttk.Button(button_frame, text="Abbrechen", command=self.window.destroy).pack(side=tk.LEFT, padx=5)
|
|
|
|
def _ok(self):
|
|
if self.dialog_type == "add":
|
|
self.result = self.name_entry.get().strip()
|
|
else: # delete
|
|
self.result = self.category_combobox.get().strip()
|
|
self.window.destroy()
|
|
|
|
@staticmethod
|
|
def add_category(parent: tk.Widget) -> Optional[str]:
|
|
dialog = CategoryDialog(parent, "add")
|
|
return dialog.show()
|
|
|
|
@staticmethod
|
|
def delete_category(parent: tk.Widget, categories: List[str]) -> Optional[str]:
|
|
dialog = CategoryDialog(parent, "delete", categories)
|
|
return dialog.show()
|
|
|
|
@staticmethod
|
|
def confirm_remove_category(parent: tk.Widget, category: str) -> bool:
|
|
return messagebox.askyesno("Bestätigung",
|
|
Config.MESSAGES["confirm_remove_category"].format(category),
|
|
parent=parent)
|
|
|
|
@staticmethod
|
|
def confirm_remove_program(parent: tk.Widget, program_name: str) -> bool:
|
|
return messagebox.askyesno("Bestätigung",
|
|
f"Möchten Sie '{program_name}' wirklich entfernen?",
|
|
parent=parent) |