diff --git a/GUI_README.md b/GUI_README.md new file mode 100644 index 0000000..d8a1858 --- /dev/null +++ b/GUI_README.md @@ -0,0 +1,73 @@ +# 🎨 GUI Installation + +Die grafische Benutzeroberfläche benötigt Tkinter, das auf manchen Systemen separat installiert werden muss. + +## Installation von Tkinter + +### Ubuntu/Debian +```bash +sudo apt-get update +sudo apt-get install python3-tk +``` + +### Fedora/RHEL +```bash +sudo dnf install python3-tkinter +``` + +### Arch Linux +```bash +sudo pacman -S tk +``` + +### macOS +Tkinter ist bereits mit Python installiert - nichts zu tun! ✓ + +### Windows +Tkinter ist bereits mit Python installiert - nichts zu tun! ✓ + +## GUI starten + +Nach der Tkinter-Installation: + +**Linux/macOS:** +```bash +./start_gui.sh +``` + +**Windows:** +``` +Doppelklick auf start_gui.cmd +``` + +## GUI-Features + +✨ **Drag & Drop-Alternative:** Klicken Sie auf "PDF hinzufügen" +📋 **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 + +## Fehlerbehebung + +### "No module named 'tkinter'" +→ Tkinter muss installiert werden (siehe oben) + +### GUI startet nicht +→ Versuchen Sie: +```bash +rm -rf .venv +./start_gui.sh +``` + +### Fenster erscheint nicht +→ Stellen Sie sicher, dass Sie eine grafische Oberfläche haben (kein SSH ohne X11) + +## Alternative: CLI-Version + +Falls Tkinter nicht installiert werden kann, nutzen Sie die CLI-Version: +```bash +./start.sh +``` + +Die CLI-Version funktioniert überall ohne zusätzliche Installation! 🚀 diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..a23f7c3 --- /dev/null +++ b/gui.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +""" +GUI für PDF zu ICS Konverter +Grafische Benutzeroberfläche mit Tkinter +""" + +import tkinter as tk +from tkinter import ttk, filedialog, messagebox, scrolledtext +from pathlib import Path +import threading +from pdf_to_ics import extract_dienstplan_data, create_ics_from_dienstplan + + +class PDFtoICSGUI: + def __init__(self, root): + self.root = root + self.root.title("PDF zu ICS Konverter - Dienstplan Importer") + self.root.geometry("800x600") + self.root.minsize(700, 500) + + # Variablen + self.pdf_files = [] + self.output_dir = tk.StringVar(value=str(Path.cwd())) + + # UI erstellen + self.create_widgets() + + def create_widgets(self): + """Erstelle die UI-Komponenten""" + + # Header + header_frame = tk.Frame(self.root, bg="#2c3e50", height=80) + header_frame.pack(fill=tk.X) + header_frame.pack_propagate(False) + + title_label = tk.Label( + header_frame, + text="📅 PDF zu ICS Konverter", + font=("Arial", 20, "bold"), + bg="#2c3e50", + fg="white" + ) + title_label.pack(pady=20) + + # Main Content Frame + content_frame = tk.Frame(self.root, padx=20, pady=20) + content_frame.pack(fill=tk.BOTH, expand=True) + + # PDF-Dateien Bereich + pdf_label = tk.Label( + content_frame, + text="PDF-Dateien:", + font=("Arial", 12, "bold") + ) + pdf_label.pack(anchor=tk.W, pady=(0, 5)) + + # Listbox mit Scrollbar für PDFs + list_frame = tk.Frame(content_frame) + list_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) + + scrollbar = tk.Scrollbar(list_frame) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + self.pdf_listbox = tk.Listbox( + list_frame, + selectmode=tk.EXTENDED, + yscrollcommand=scrollbar.set, + font=("Arial", 10) + ) + self.pdf_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scrollbar.config(command=self.pdf_listbox.yview) + + # Buttons für PDF-Verwaltung + button_frame = tk.Frame(content_frame) + button_frame.pack(fill=tk.X, pady=(0, 10)) + + add_btn = tk.Button( + button_frame, + text="➕ PDF hinzufügen", + command=self.add_pdf_files, + bg="#3498db", + fg="white", + font=("Arial", 10, "bold"), + padx=20, + pady=8, + cursor="hand2" + ) + add_btn.pack(side=tk.LEFT, padx=(0, 5)) + + remove_btn = tk.Button( + button_frame, + text="➖ Entfernen", + command=self.remove_selected_pdfs, + bg="#e74c3c", + fg="white", + font=("Arial", 10, "bold"), + padx=20, + pady=8, + cursor="hand2" + ) + remove_btn.pack(side=tk.LEFT, padx=(0, 5)) + + clear_btn = tk.Button( + button_frame, + text="🗑️ Alle entfernen", + command=self.clear_all_pdfs, + bg="#95a5a6", + fg="white", + font=("Arial", 10, "bold"), + padx=20, + pady=8, + cursor="hand2" + ) + clear_btn.pack(side=tk.LEFT) + + # Ausgabe-Verzeichnis + output_frame = tk.Frame(content_frame) + output_frame.pack(fill=tk.X, pady=(10, 10)) + + output_label = tk.Label( + output_frame, + text="Ausgabe-Verzeichnis:", + font=("Arial", 10) + ) + output_label.pack(side=tk.LEFT, padx=(0, 5)) + + output_entry = tk.Entry( + output_frame, + textvariable=self.output_dir, + font=("Arial", 10), + state="readonly" + ) + output_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) + + browse_btn = tk.Button( + output_frame, + text="📁 Durchsuchen", + command=self.browse_output_dir, + bg="#16a085", + fg="white", + font=("Arial", 10), + padx=15, + pady=5, + cursor="hand2" + ) + browse_btn.pack(side=tk.LEFT) + + # Log-Bereich + log_label = tk.Label( + content_frame, + text="Status:", + font=("Arial", 10, "bold") + ) + log_label.pack(anchor=tk.W, pady=(10, 5)) + + self.log_text = scrolledtext.ScrolledText( + content_frame, + height=8, + font=("Consolas", 9), + bg="#f8f9fa", + state=tk.DISABLED + ) + 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)) + + # Initial log message + self.log("Bereit. Fügen Sie PDF-Dateien hinzu um zu starten.") + + def log(self, message): + """Füge eine Nachricht zum Log hinzu""" + self.log_text.config(state=tk.NORMAL) + self.log_text.insert(tk.END, message + "\n") + self.log_text.see(tk.END) + self.log_text.config(state=tk.DISABLED) + + def add_pdf_files(self): + """Öffne Datei-Dialog zum Hinzufügen von PDFs""" + files = filedialog.askopenfilenames( + title="PDF-Dateien auswählen", + filetypes=[("PDF Dateien", "*.pdf"), ("Alle Dateien", "*.*")] + ) + + for file in files: + if file not in self.pdf_files: + self.pdf_files.append(file) + self.pdf_listbox.insert(tk.END, Path(file).name) + + if files: + self.log(f"✓ {len(files)} PDF-Datei(en) hinzugefügt") + + def remove_selected_pdfs(self): + """Entferne ausgewählte PDFs aus der Liste""" + selected = self.pdf_listbox.curselection() + + # Rückwärts durchlaufen, um Indexprobleme zu vermeiden + for index in reversed(selected): + self.pdf_listbox.delete(index) + del self.pdf_files[index] + + if selected: + self.log(f"✓ {len(selected)} PDF-Datei(en) entfernt") + + def clear_all_pdfs(self): + """Entferne alle PDFs aus der Liste""" + count = len(self.pdf_files) + self.pdf_listbox.delete(0, tk.END) + self.pdf_files.clear() + + if count > 0: + self.log(f"✓ Alle {count} PDF-Datei(en) entfernt") + + def browse_output_dir(self): + """Öffne Dialog zur Auswahl des Ausgabe-Verzeichnisses""" + directory = filedialog.askdirectory( + title="Ausgabe-Verzeichnis auswählen", + initialdir=self.output_dir.get() + ) + + if directory: + self.output_dir.set(directory) + self.log(f"✓ Ausgabe-Verzeichnis: {directory}") + + def convert_pdfs(self): + """Konvertiere alle PDFs zu ICS""" + if not self.pdf_files: + messagebox.showwarning( + "Keine PDFs", + "Bitte fügen Sie mindestens eine PDF-Datei hinzu." + ) + return + + # Starte Konvertierung in separatem Thread + thread = threading.Thread(target=self._convert_worker, daemon=True) + thread.start() + + def _convert_worker(self): + """Worker-Thread für Konvertierung""" + self.progress['maximum'] = len(self.pdf_files) + self.progress['value'] = 0 + + output_dir = Path(self.output_dir.get()) + success_count = 0 + + self.log("\n" + "="*50) + self.log("🔄 Starte Konvertierung...") + self.log("="*50) + + for i, pdf_path in enumerate(self.pdf_files, 1): + try: + self.log(f"\n[{i}/{len(self.pdf_files)}] Verarbeite: {Path(pdf_path).name}") + + # Extrahiere Daten + dienstplan = extract_dienstplan_data(pdf_path) + + # Zeige Informationen + self.log(f" ├─ Name: {dienstplan['vorname']} {dienstplan['name']}") + self.log(f" ├─ Personalnummer: {dienstplan['personalnummer']}") + self.log(f" ├─ Betriebshof: {dienstplan['betriebshof']}") + self.log(f" └─ Events gefunden: {len(dienstplan['events'])}") + + if not dienstplan['events']: + self.log(" ⚠️ Warnung: Keine Events gefunden!") + continue + + # Erstelle ICS-Datei + ics_filename = Path(pdf_path).stem + '.ics' + ics_path = output_dir / ics_filename + + create_ics_from_dienstplan(dienstplan, str(ics_path)) + + self.log(f" ✓ ICS erstellt: {ics_filename}") + success_count += 1 + + except Exception as e: + self.log(f" ✗ Fehler: {str(e)}") + + finally: + self.progress['value'] = i + + # Zusammenfassung + self.log("\n" + "="*50) + self.log(f"✅ Fertig! {success_count}/{len(self.pdf_files)} ICS-Dateien erstellt") + self.log("="*50 + "\n") + + # Erfolgsmeldung + if success_count > 0: + self.root.after(0, lambda: messagebox.showinfo( + "Konvertierung abgeschlossen", + f"Es wurden {success_count} ICS-Datei(en) erfolgreich erstellt!\n\n" + f"Speicherort: {output_dir}" + )) + + +def main(): + """Hauptfunktion""" + root = tk.Tk() + app = PDFtoICSGUI(root) + root.mainloop() + + +if __name__ == '__main__': + main() diff --git a/start_gui.cmd b/start_gui.cmd new file mode 100644 index 0000000..fb89220 --- /dev/null +++ b/start_gui.cmd @@ -0,0 +1,34 @@ +@echo off +REM PDF zu ICS Konverter - GUI Startskript (Windows) + +setlocal enabledelayedexpansion + +REM Wechsel ins Skriptverzeichnis +cd /d "%~dp0" + +REM Überprüfe, ob venv existiert +if not exist ".venv" ( + echo 📦 Python-Umgebung wird eingerichtet... + python3 -m venv .venv --upgrade-deps + if errorlevel 1 ( + python -m venv .venv --upgrade-deps + ) +) + +REM Überprüfe, ob Abhängigkeiten installiert sind +.venv\Scripts\python.exe -c "import pdfplumber" 2>nul +if errorlevel 1 ( + echo 📚 Installiere Abhängigkeiten... + call .venv\Scripts\python.exe -m pip install -q pdfplumber icalendar pypdf2 pytz + echo ✓ Abhängigkeiten installiert +) + +REM Starte die GUI +echo 🎨 Starte GUI... +call .venv\Scripts\pythonw.exe gui.py + +if errorlevel 1 ( + echo. + echo ❌ Fehler beim Starten der GUI + pause +) diff --git a/start_gui.sh b/start_gui.sh new file mode 100755 index 0000000..09585ec --- /dev/null +++ b/start_gui.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# PDF zu ICS Konverter - GUI Startskript + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +# Finde Python-Executable +PYTHON_CMD="" +if command -v python3 &> /dev/null; then + PYTHON_CMD="python3" +elif command -v python &> /dev/null; then + PYTHON_CMD="python" +else + echo "❌ Fehler: Python nicht gefunden!" + echo "Bitte installieren Sie Python 3.6 oder höher." + exit 1 +fi + +echo "🐍 Nutze: $PYTHON_CMD" + +# Erstelle venv wenn nicht vorhanden +if [ ! -d ".venv" ]; then + echo "📦 Python-Umgebung wird eingerichtet..." + $PYTHON_CMD -m venv .venv --upgrade-deps || { + echo "❌ venv konnte nicht erstellt werden" + exit 1 + } +fi + +# Nutze Python aus venv +PYTHON_VENV=".venv/bin/python" + +# Überprüfe, ob Abhängigkeiten installiert sind +if ! $PYTHON_VENV -c "import pdfplumber" 2>/dev/null; then + echo "📚 Installiere Abhängigkeiten..." + + if $PYTHON_VENV -m pip install -q pdfplumber icalendar pypdf2 pytz 2>/dev/null; then + echo "✓ Abhängigkeiten installiert" + else + echo "❌ Installation fehlgeschlagen" + echo "🔧 Versuche venv neu aufzubauen..." + rm -rf .venv + $PYTHON_CMD -m venv .venv --upgrade-deps || { + echo "❌ venv konnte nicht neu erstellt werden" + exit 1 + } + $PYTHON_VENV -m pip install -q pdfplumber icalendar pypdf2 pytz || { + echo "❌ Abhängigkeiten konnten nicht installiert werden" + exit 1 + } + echo "✓ venv neu erstellt" + fi +fi + +# Starte die GUI +echo "🎨 Starte GUI..." +if [ -f "$PYTHON_VENV" ]; then + $PYTHON_VENV gui.py +else + echo "❌ Fehler: Python-Umgebung ist beschädigt" + echo "📁 Bitte löschen Sie das .venv Verzeichnis und versuchen Sie erneut:" + echo " rm -rf .venv" + echo " ./start_gui.sh" + exit 1 +fi