GUI Verbesserungen: Drag & Drop, Verzeichnis-Speicherung und UI-Optimierungen
- Drag & Drop für PDF-Dateien hinzugefügt (mit tkinterdnd2) - Letzte Verzeichnisse werden in ~/.pdf_to_ics_config.json gespeichert - Konvertieren-Button kompakter neben 'Alle entfernen' platziert - Button umbenannt zu 'ICS Datei erstellen' - Automatische Installation von tkinterdnd2 im Startskript - .gitignore erweitert um Config-Datei
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
*.pdf
|
||||
.venv/
|
||||
__pycache__/
|
||||
.pdf_to_ics_config.json
|
||||
|
||||
@@ -42,12 +42,22 @@ Doppelklick auf start_gui.cmd
|
||||
|
||||
## GUI-Features
|
||||
|
||||
✨ **Drag & Drop-Alternative:** Klicken Sie auf "PDF hinzufügen"
|
||||
✨ **Drag & Drop:** Ziehen Sie PDF-Dateien direkt in die Liste (optional mit tkinterdnd2)
|
||||
📋 **Mehrere PDFs:** Wählen Sie mehrere Dateien gleichzeitig
|
||||
📁 **Ausgabe-Verzeichnis:** Wählen Sie, wo die ICS-Dateien gespeichert werden
|
||||
📊 **Echtzeit-Log:** Sehen Sie den Fortschritt live
|
||||
✅ **Fortschrittsbalken:** Visuelles Feedback bei der Konvertierung
|
||||
|
||||
### Drag & Drop aktivieren (optional)
|
||||
|
||||
Für besseres Drag & Drop installieren Sie tkinterdnd2:
|
||||
|
||||
```bash
|
||||
.venv/bin/pip install tkinterdnd2
|
||||
```
|
||||
|
||||
Oder lassen Sie das Startskript es automatisch installieren.
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
### "No module named 'tkinter'"
|
||||
|
||||
167
gui.py
167
gui.py
@@ -8,8 +8,20 @@ import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||
from pathlib import Path
|
||||
import threading
|
||||
import re
|
||||
import json
|
||||
from pdf_to_ics import extract_dienstplan_data, create_ics_from_dienstplan
|
||||
|
||||
# Versuche tkinterdnd2 zu importieren (optional für besseres Drag & Drop)
|
||||
try:
|
||||
from tkinterdnd2 import DND_FILES, TkinterDnD
|
||||
HAS_TKINTERDND = True
|
||||
except ImportError:
|
||||
HAS_TKINTERDND = False
|
||||
|
||||
# Konfigurationsdatei
|
||||
CONFIG_FILE = Path.home() / '.pdf_to_ics_config.json'
|
||||
|
||||
|
||||
class PDFtoICSGUI:
|
||||
def __init__(self, root):
|
||||
@@ -18,13 +30,32 @@ class PDFtoICSGUI:
|
||||
self.root.geometry("800x600")
|
||||
self.root.minsize(700, 500)
|
||||
|
||||
# Lade gespeicherte Einstellungen
|
||||
self.config = self.load_config()
|
||||
|
||||
# Variablen
|
||||
self.pdf_files = []
|
||||
self.output_dir = tk.StringVar(value=str(Path.cwd()))
|
||||
|
||||
# Nutze letztes Ausgabeverzeichnis oder Standard
|
||||
default_dir = self.config.get('last_output_dir', None)
|
||||
if not default_dir or not Path(default_dir).exists():
|
||||
default_dir = Path.cwd()
|
||||
if str(default_dir).split('/')[-1].startswith('.'):
|
||||
default_dir = Path.home()
|
||||
self.output_dir = tk.StringVar(value=str(default_dir))
|
||||
|
||||
# Letztes PDF-Verzeichnis merken
|
||||
self.last_pdf_dir = self.config.get('last_pdf_dir', str(Path.home()))
|
||||
|
||||
# UI erstellen
|
||||
self.create_widgets()
|
||||
|
||||
# Drag & Drop einrichten
|
||||
self.setup_drag_and_drop()
|
||||
|
||||
# Speichere Konfiguration beim Schließen
|
||||
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||
|
||||
def create_widgets(self):
|
||||
"""Erstelle die UI-Komponenten"""
|
||||
|
||||
@@ -113,6 +144,20 @@ class PDFtoICSGUI:
|
||||
)
|
||||
clear_btn.pack(side=tk.LEFT)
|
||||
|
||||
# Konvertieren Button (rechts in der Zeile)
|
||||
self.convert_btn = tk.Button(
|
||||
button_frame,
|
||||
text="📄 ICS Datei erstellen",
|
||||
command=self.convert_pdfs,
|
||||
bg="#27ae60",
|
||||
fg="white",
|
||||
font=("Arial", 10, "bold"),
|
||||
padx=20,
|
||||
pady=8,
|
||||
cursor="hand2"
|
||||
)
|
||||
self.convert_btn.pack(side=tk.RIGHT)
|
||||
|
||||
# Ausgabe-Verzeichnis
|
||||
output_frame = tk.Frame(content_frame)
|
||||
output_frame.pack(fill=tk.X, pady=(10, 10))
|
||||
@@ -162,30 +207,89 @@ class PDFtoICSGUI:
|
||||
)
|
||||
self.log_text.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Konvertieren Button (groß und zentral)
|
||||
convert_btn = tk.Button(
|
||||
content_frame,
|
||||
text="🔄 PDFs konvertieren",
|
||||
command=self.convert_pdfs,
|
||||
bg="#27ae60",
|
||||
fg="white",
|
||||
font=("Arial", 14, "bold"),
|
||||
padx=30,
|
||||
pady=15,
|
||||
cursor="hand2"
|
||||
)
|
||||
convert_btn.pack(pady=20)
|
||||
|
||||
# Fortschrittsbalken
|
||||
self.progress = ttk.Progressbar(
|
||||
content_frame,
|
||||
mode='determinate',
|
||||
length=300
|
||||
)
|
||||
self.progress.pack(pady=(0, 10))
|
||||
drag_info = " (Drag & Drop unterstützt)" if HAS_TKINTERDND else " (Tipp: Installiere tkinterdnd2 für Drag & Drop)"
|
||||
self.log(f"Bereit. Fügen Sie PDF-Dateien hinzu um zu starten.{drag_info}")
|
||||
|
||||
# Initial log message
|
||||
self.log("Bereit. Fügen Sie PDF-Dateien hinzu um zu starten.")
|
||||
def load_config(self):
|
||||
"""Lade gespeicherte Konfiguration"""
|
||||
try:
|
||||
if CONFIG_FILE.exists():
|
||||
with open(CONFIG_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Warnung: Konfiguration konnte nicht geladen werden: {e}")
|
||||
return {}
|
||||
|
||||
def save_config(self):
|
||||
"""Speichere Konfiguration"""
|
||||
try:
|
||||
config = {
|
||||
'last_output_dir': self.output_dir.get(),
|
||||
'last_pdf_dir': self.last_pdf_dir
|
||||
}
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
except Exception as e:
|
||||
print(f"Warnung: Konfiguration konnte nicht gespeichert werden: {e}")
|
||||
|
||||
def on_closing(self):
|
||||
"""Handle für Fenster schließen"""
|
||||
self.save_config()
|
||||
self.root.destroy()
|
||||
|
||||
def setup_drag_and_drop(self):
|
||||
"""Richte Drag & Drop ein"""
|
||||
if HAS_TKINTERDND:
|
||||
# Verwende tkinterdnd2 wenn verfügbar
|
||||
self.pdf_listbox.drop_target_register(DND_FILES)
|
||||
self.pdf_listbox.dnd_bind('<<Drop>>', self.drop_files)
|
||||
else:
|
||||
# Fallback: Native Tkinter Drag & Drop (funktioniert auf Unix)
|
||||
try:
|
||||
self.pdf_listbox.drop_target_register('DND_Files')
|
||||
self.pdf_listbox.dnd_bind('<<Drop>>', self.drop_files)
|
||||
except:
|
||||
# Wenn auch das nicht funktioniert, zeige Hilfstext
|
||||
pass
|
||||
|
||||
def drop_files(self, event):
|
||||
"""Handle für Drag & Drop Events"""
|
||||
files = []
|
||||
|
||||
if HAS_TKINTERDND:
|
||||
# Parse tkinterdnd2 format
|
||||
files_str = event.data
|
||||
# Entferne geschweifte Klammern und splitte
|
||||
files_str = files_str.strip('{}')
|
||||
files = re.findall(r'[^\s{}]+(?:\s+[^\s{}]+)*', files_str)
|
||||
else:
|
||||
# Fallback parsing
|
||||
if hasattr(event, 'data'):
|
||||
files_str = event.data.strip('{}')
|
||||
files = [f.strip() for f in files_str.split()]
|
||||
|
||||
# Füge nur PDF-Dateien hinzu
|
||||
pdf_count = 0
|
||||
for file_path in files:
|
||||
file_path = file_path.strip()
|
||||
if file_path.lower().endswith('.pdf'):
|
||||
if file_path not in self.pdf_files:
|
||||
self.pdf_files.append(file_path)
|
||||
self.pdf_listbox.insert(tk.END, Path(file_path).name)
|
||||
pdf_count += 1
|
||||
|
||||
if pdf_count > 0:
|
||||
self.log(f"✓ {pdf_count} PDF-Datei(en) per Drag & Drop hinzugefügt")
|
||||
elif files:
|
||||
self.log("⚠ Nur PDF-Dateien können hinzugefügt werden")
|
||||
|
||||
return 'break'
|
||||
|
||||
def log(self, message):
|
||||
"""Füge eine Nachricht zum Log hinzu"""
|
||||
@@ -196,9 +300,15 @@ class PDFtoICSGUI:
|
||||
|
||||
def add_pdf_files(self):
|
||||
"""Öffne Datei-Dialog zum Hinzufügen von PDFs"""
|
||||
# Starte im letzten PDF-Verzeichnis
|
||||
initial_dir = self.last_pdf_dir
|
||||
if initial_dir.startswith('.') or not Path(initial_dir).exists():
|
||||
initial_dir = str(Path.home())
|
||||
|
||||
files = filedialog.askopenfilenames(
|
||||
title="PDF-Dateien auswählen",
|
||||
filetypes=[("PDF Dateien", "*.pdf"), ("Alle Dateien", "*.*")]
|
||||
filetypes=[("PDF Dateien", "*.pdf"), ("Alle Dateien", "*.*")],
|
||||
initialdir=initial_dir
|
||||
)
|
||||
|
||||
for file in files:
|
||||
@@ -207,6 +317,8 @@ class PDFtoICSGUI:
|
||||
self.pdf_listbox.insert(tk.END, Path(file).name)
|
||||
|
||||
if files:
|
||||
# Merke Verzeichnis der ersten ausgewählten Datei
|
||||
self.last_pdf_dir = str(Path(files[0]).parent)
|
||||
self.log(f"✓ {len(files)} PDF-Datei(en) hinzugefügt")
|
||||
|
||||
def remove_selected_pdfs(self):
|
||||
@@ -232,13 +344,20 @@ class PDFtoICSGUI:
|
||||
|
||||
def browse_output_dir(self):
|
||||
"""Öffne Dialog zur Auswahl des Ausgabe-Verzeichnisses"""
|
||||
# Verhindere Start in versteckten Verzeichnissen
|
||||
initial_dir = self.output_dir.get()
|
||||
if initial_dir.startswith('.') or '/.venv' in initial_dir or '/__pycache__' in initial_dir:
|
||||
initial_dir = str(Path.home())
|
||||
|
||||
directory = filedialog.askdirectory(
|
||||
title="Ausgabe-Verzeichnis auswählen",
|
||||
initialdir=self.output_dir.get()
|
||||
initialdir=initial_dir,
|
||||
mustexist=True
|
||||
)
|
||||
|
||||
if directory:
|
||||
self.output_dir.set(directory)
|
||||
self.save_config() # Sofort speichern
|
||||
self.log(f"✓ Ausgabe-Verzeichnis: {directory}")
|
||||
|
||||
def convert_pdfs(self):
|
||||
@@ -314,7 +433,13 @@ class PDFtoICSGUI:
|
||||
|
||||
def main():
|
||||
"""Hauptfunktion"""
|
||||
root = tk.Tk()
|
||||
if HAS_TKINTERDND:
|
||||
# Verwende TkinterDnD root für besseres Drag & Drop
|
||||
root = TkinterDnD.Tk()
|
||||
else:
|
||||
# Standard Tkinter
|
||||
root = tk.Tk()
|
||||
|
||||
app = PDFtoICSGUI(root)
|
||||
root.mainloop()
|
||||
|
||||
|
||||
@@ -53,6 +53,12 @@ if ! $PYTHON_VENV -c "import pdfplumber" 2>/dev/null; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Versuche tkinterdnd2 zu installieren (optional für Drag & Drop)
|
||||
if ! $PYTHON_VENV -c "import tkinterdnd2" 2>/dev/null; then
|
||||
echo "💡 Installiere tkinterdnd2 für Drag & Drop (optional)..."
|
||||
$PYTHON_VENV -m pip install -q tkinterdnd2 2>/dev/null && echo "✓ Drag & Drop aktiviert" || echo "ℹ️ Drag & Drop nicht verfügbar (kein Problem)"
|
||||
fi
|
||||
|
||||
# Starte die GUI
|
||||
echo "🎨 Starte GUI..."
|
||||
if [ -f "$PYTHON_VENV" ]; then
|
||||
|
||||
Reference in New Issue
Block a user