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
|
*.pdf
|
||||||
.venv/
|
.venv/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
.pdf_to_ics_config.json
|
||||||
|
|||||||
@@ -42,12 +42,22 @@ Doppelklick auf start_gui.cmd
|
|||||||
|
|
||||||
## GUI-Features
|
## 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
|
📋 **Mehrere PDFs:** Wählen Sie mehrere Dateien gleichzeitig
|
||||||
📁 **Ausgabe-Verzeichnis:** Wählen Sie, wo die ICS-Dateien gespeichert werden
|
📁 **Ausgabe-Verzeichnis:** Wählen Sie, wo die ICS-Dateien gespeichert werden
|
||||||
📊 **Echtzeit-Log:** Sehen Sie den Fortschritt live
|
📊 **Echtzeit-Log:** Sehen Sie den Fortschritt live
|
||||||
✅ **Fortschrittsbalken:** Visuelles Feedback bei der Konvertierung
|
✅ **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
|
## Fehlerbehebung
|
||||||
|
|
||||||
### "No module named 'tkinter'"
|
### "No module named 'tkinter'"
|
||||||
|
|||||||
165
gui.py
165
gui.py
@@ -8,8 +8,20 @@ import tkinter as tk
|
|||||||
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import threading
|
import threading
|
||||||
|
import re
|
||||||
|
import json
|
||||||
from pdf_to_ics import extract_dienstplan_data, create_ics_from_dienstplan
|
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:
|
class PDFtoICSGUI:
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
@@ -18,13 +30,32 @@ class PDFtoICSGUI:
|
|||||||
self.root.geometry("800x600")
|
self.root.geometry("800x600")
|
||||||
self.root.minsize(700, 500)
|
self.root.minsize(700, 500)
|
||||||
|
|
||||||
|
# Lade gespeicherte Einstellungen
|
||||||
|
self.config = self.load_config()
|
||||||
|
|
||||||
# Variablen
|
# Variablen
|
||||||
self.pdf_files = []
|
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
|
# UI erstellen
|
||||||
self.create_widgets()
|
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):
|
def create_widgets(self):
|
||||||
"""Erstelle die UI-Komponenten"""
|
"""Erstelle die UI-Komponenten"""
|
||||||
|
|
||||||
@@ -113,6 +144,20 @@ class PDFtoICSGUI:
|
|||||||
)
|
)
|
||||||
clear_btn.pack(side=tk.LEFT)
|
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
|
# Ausgabe-Verzeichnis
|
||||||
output_frame = tk.Frame(content_frame)
|
output_frame = tk.Frame(content_frame)
|
||||||
output_frame.pack(fill=tk.X, pady=(10, 10))
|
output_frame.pack(fill=tk.X, pady=(10, 10))
|
||||||
@@ -162,30 +207,89 @@ class PDFtoICSGUI:
|
|||||||
)
|
)
|
||||||
self.log_text.pack(fill=tk.BOTH, expand=True)
|
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
|
# Fortschrittsbalken
|
||||||
self.progress = ttk.Progressbar(
|
self.progress = ttk.Progressbar(
|
||||||
content_frame,
|
content_frame,
|
||||||
mode='determinate',
|
mode='determinate',
|
||||||
length=300
|
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
|
def load_config(self):
|
||||||
self.log("Bereit. Fügen Sie PDF-Dateien hinzu um zu starten.")
|
"""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):
|
def log(self, message):
|
||||||
"""Füge eine Nachricht zum Log hinzu"""
|
"""Füge eine Nachricht zum Log hinzu"""
|
||||||
@@ -196,9 +300,15 @@ class PDFtoICSGUI:
|
|||||||
|
|
||||||
def add_pdf_files(self):
|
def add_pdf_files(self):
|
||||||
"""Öffne Datei-Dialog zum Hinzufügen von PDFs"""
|
"""Ö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(
|
files = filedialog.askopenfilenames(
|
||||||
title="PDF-Dateien auswählen",
|
title="PDF-Dateien auswählen",
|
||||||
filetypes=[("PDF Dateien", "*.pdf"), ("Alle Dateien", "*.*")]
|
filetypes=[("PDF Dateien", "*.pdf"), ("Alle Dateien", "*.*")],
|
||||||
|
initialdir=initial_dir
|
||||||
)
|
)
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
@@ -207,6 +317,8 @@ class PDFtoICSGUI:
|
|||||||
self.pdf_listbox.insert(tk.END, Path(file).name)
|
self.pdf_listbox.insert(tk.END, Path(file).name)
|
||||||
|
|
||||||
if files:
|
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")
|
self.log(f"✓ {len(files)} PDF-Datei(en) hinzugefügt")
|
||||||
|
|
||||||
def remove_selected_pdfs(self):
|
def remove_selected_pdfs(self):
|
||||||
@@ -232,13 +344,20 @@ class PDFtoICSGUI:
|
|||||||
|
|
||||||
def browse_output_dir(self):
|
def browse_output_dir(self):
|
||||||
"""Öffne Dialog zur Auswahl des Ausgabe-Verzeichnisses"""
|
"""Ö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(
|
directory = filedialog.askdirectory(
|
||||||
title="Ausgabe-Verzeichnis auswählen",
|
title="Ausgabe-Verzeichnis auswählen",
|
||||||
initialdir=self.output_dir.get()
|
initialdir=initial_dir,
|
||||||
|
mustexist=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if directory:
|
if directory:
|
||||||
self.output_dir.set(directory)
|
self.output_dir.set(directory)
|
||||||
|
self.save_config() # Sofort speichern
|
||||||
self.log(f"✓ Ausgabe-Verzeichnis: {directory}")
|
self.log(f"✓ Ausgabe-Verzeichnis: {directory}")
|
||||||
|
|
||||||
def convert_pdfs(self):
|
def convert_pdfs(self):
|
||||||
@@ -314,7 +433,13 @@ class PDFtoICSGUI:
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Hauptfunktion"""
|
"""Hauptfunktion"""
|
||||||
|
if HAS_TKINTERDND:
|
||||||
|
# Verwende TkinterDnD root für besseres Drag & Drop
|
||||||
|
root = TkinterDnD.Tk()
|
||||||
|
else:
|
||||||
|
# Standard Tkinter
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
|
|
||||||
app = PDFtoICSGUI(root)
|
app = PDFtoICSGUI(root)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ if ! $PYTHON_VENV -c "import pdfplumber" 2>/dev/null; then
|
|||||||
fi
|
fi
|
||||||
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
|
# Starte die GUI
|
||||||
echo "🎨 Starte GUI..."
|
echo "🎨 Starte GUI..."
|
||||||
if [ -f "$PYTHON_VENV" ]; then
|
if [ -f "$PYTHON_VENV" ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user