Release 1.2.0: wxPython migration + vacation exclusion

This commit is contained in:
2026-03-02 17:37:10 +01:00
parent 07c8905f47
commit db76fbf0d2
14 changed files with 190 additions and 939 deletions

View File

@@ -1,35 +1,9 @@
# 🎨 GUI Installation # 🎨 GUI Installation (wxPython)
Die grafische Benutzeroberfläche benötigt Tkinter, das auf manchen Systemen separat installiert werden muss. Die grafische Benutzeroberfläche nutzt **wxPython** für ein natives Look & Feel auf Linux, macOS und Windows.
## 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 ## GUI starten
Nach der Tkinter-Installation:
**Linux/macOS:** **Linux/macOS:**
```bash ```bash
./start_gui.sh ./start_gui.sh
@@ -40,44 +14,41 @@ Nach der Tkinter-Installation:
Doppelklick auf start_gui.cmd Doppelklick auf start_gui.cmd
``` ```
Beim ersten Start werden `.venv`, Kern-Abhängigkeiten und `wxPython` automatisch installiert.
## GUI-Features ## GUI-Features
**Drag & Drop:** Ziehen Sie PDF-Dateien direkt in die Liste (optional mit tkinterdnd2) **Native Oberfläche:** Optisch passend zum Betriebssystem
📋 **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 🖱️ **Drag & Drop:** Direkt in die PDF-Liste ziehen
### 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 'wx'"
Tkinter muss installiert werden (siehe oben) `wxPython` konnte nicht installiert werden.
Unter Linux Mint/Ubuntu helfen häufig:
```bash
sudo apt-get update
sudo apt-get install -y build-essential python3-dev libgtk-3-dev libglib2.0-dev libjpeg-dev libtiff-dev libpng-dev
rm -rf .venv
./start_gui.sh
```
### GUI startet nicht ### GUI startet nicht
→ Versuchen Sie:
```bash ```bash
rm -rf .venv rm -rf .venv
./start_gui.sh ./start_gui.sh
``` ```
### Fenster erscheint nicht ### Fenster erscheint nicht
→ Stellen Sie sicher, dass Sie eine grafische Oberfläche haben (kein SSH ohne X11) → Stellen Sie sicher, dass eine grafische Sitzung aktiv ist (kein reines SSH ohne X11/Wayland-Forwarding).
## Alternative: CLI-Version ## Alternative: CLI-Version
Falls Tkinter nicht installiert werden kann, nutzen Sie die CLI-Version: Falls keine GUI möglich ist:
```bash ```bash
./start.sh ./start.sh
``` ```
Die CLI-Version funktioniert überall ohne zusätzliche Installation! 🚀

View File

@@ -14,7 +14,7 @@ Das war's! Die Anwendung erscheint nun in Ihrem Anwendungsmenü unter "PDF zu IC
## 📋 Was macht das Installations-Script? ## 📋 Was macht das Installations-Script?
1.**Prüft Python-Installation** (Python 3.6+) 1.**Prüft Python-Installation** (Python 3.6+)
2.**Installiert Tkinter** falls nötig (mit sudo-Berechtigung) 2.**Installiert wxPython** in der virtuellen Umgebung
3.**Erstellt Installationsverzeichnis** in `~/.local/share/pdf-to-ics` 3.**Erstellt Installationsverzeichnis** in `~/.local/share/pdf-to-ics`
4.**Kopiert alle Dateien** ins Installationsverzeichnis 4.**Kopiert alle Dateien** ins Installationsverzeichnis
5.**Erstellt Python Virtual Environment** mit allen Abhängigkeiten 5.**Erstellt Python Virtual Environment** mit allen Abhängigkeiten
@@ -43,14 +43,14 @@ pdf-to-ics
## 🔧 Systemanforderungen ## 🔧 Systemanforderungen
### Unterstützte Distributionen: ### Unterstützte Distributionen:
- ✅ Ubuntu / Debian (automatische Tkinter-Installation) - ✅ Ubuntu / Debian / Linux Mint
- ✅ Fedora / RHEL (automatische Tkinter-Installation) - ✅ Fedora / RHEL
- ✅ Arch Linux (automatische Tkinter-Installation) - ✅ Arch Linux
- ✅ Andere Distributionen (manuelle Tkinter-Installation erforderlich) - ✅ Andere Distributionen (ggf. zusätzliche Build-Abhängigkeiten nötig)
### Voraussetzungen: ### Voraussetzungen:
- Python 3.6 oder höher - Python 3.6 oder höher
- `sudo`-Berechtigung (für Tkinter-Installation) - Internetzugang für `pip install wxPython`
- Etwa 50 MB Festplattenspeicher - Etwa 50 MB Festplattenspeicher
## 📁 Installations-Pfade ## 📁 Installations-Pfade
@@ -76,21 +76,12 @@ Das Deinstallations-Script entfernt:
## ⚠️ Fehlerbehebung ## ⚠️ Fehlerbehebung
### "Tkinter ist nicht installiert" ### "wxPython konnte nicht installiert werden"
**Ubuntu/Debian:** **Ubuntu/Debian/Linux Mint:**
```bash ```bash
sudo apt-get install python3-tk sudo apt-get update
``` sudo apt-get install -y build-essential python3-dev libgtk-3-dev libglib2.0-dev libjpeg-dev libtiff-dev libpng-dev
**Fedora:**
```bash
sudo dnf install python3-tkinter
```
**Arch Linux:**
```bash
sudo pacman -S tk
``` ```
### "pdf-to-ics: Befehl nicht gefunden" ### "pdf-to-ics: Befehl nicht gefunden"
@@ -115,7 +106,7 @@ source ~/.bashrc
```bat ```bat
py -3 -m venv .venv --upgrade-deps py -3 -m venv .venv --upgrade-deps
.\.venv\Scripts\python.exe -m pip install -q pdfplumber icalendar pypdf2 pytz packaging .\.venv\Scripts\python.exe -m pip install -q pdfplumber icalendar pypdf2 pytz packaging
.\.venv\Scripts\pythonw.exe gui.py .\.venv\Scripts\pythonw.exe gui_wxpython.py
``` ```
### Anwendung erscheint nicht im Menü ### Anwendung erscheint nicht im Menü

View File

@@ -23,9 +23,9 @@ Kopieren Sie Ihre Dienstplan-PDF-Dateien in dieses Verzeichnis:
/home/sebastian/Dokumente/ICS-Import/ /home/sebastian/Dokumente/ICS-Import/
``` ```
### 3. Konvertieren und Importieren ## 3. Konvertieren und Importieren
Im Menü wählen Sie Option "1. PDF(s) konvertieren" und die ICS-Dateien werden automatisch erstellt. In der GUI auf "ICS Datei erstellen" klicken. Die ICS-Dateien werden dann automatisch erstellt.
--- ---

View File

@@ -18,7 +18,7 @@ Benutzerfreundliche grafische Oberfläche mit Drag & Drop Support.
- 📊 Live-Log und Fortschrittsanzeige - 📊 Live-Log und Fortschrittsanzeige
- 💾 Merkt sich letzte Verzeichnisse - 💾 Merkt sich letzte Verzeichnisse
**Voraussetzung:** Tkinter muss installiert sein (siehe [GUI_README.md](GUI_README.md)) **Voraussetzung:** wxPython muss installiert sein (wird automatisch versucht; siehe [WXPYTHON_README.md](WXPYTHON_README.md))
### 2. **CLI-Version** (Kommandozeile) ### 2. **CLI-Version** (Kommandozeile)
Textbasiertes Menü für die Kommandozeile. Textbasiertes Menü für die Kommandozeile.
@@ -57,7 +57,7 @@ Beim ersten Start wird automatisch eine virtuelle Umgebung (`.venv`) erstellt un
```bat ```bat
py -3 -m venv .venv --upgrade-deps py -3 -m venv .venv --upgrade-deps
.\.venv\Scripts\python.exe gui.py .\.venv\Scripts\python.exe gui_wxpython.py
``` ```
### Schnellstart (Empfohlen) ### Schnellstart (Empfohlen)
@@ -66,40 +66,33 @@ py -3 -m venv .venv --upgrade-deps
```bash ```bash
./start_gui.sh ./start_gui.sh
``` ```
Siehe [GUI_README.md](GUI_README.md) für Tkinter-Installation. Siehe [WXPYTHON_README.md](WXPYTHON_README.md) für wxPython-Hinweise.
**Für CLI-Version:** **Für CLI-Version:**
```bash ```bash
./start.sh ./start.sh
```GUI-Version (Empfohlen) ```
### GUI-Version (Empfohlen)
1. Starten Sie die GUI: 1. Starten Sie die GUI:
```bash ```bash
./start_gui.sh ./start_gui.sh
``` ```
2. Fügen Sie PDF-Dateien hinzu (Button oder Drag & Drop).
3. Optional: Aktivieren Sie Filter wie „Ruhetage ausschließen“ und „Urlaub ausschließen (060, 0060)“.
4. Klicken Sie auf „📄 ICS Datei erstellen“.
2. Fügen Sie PDF-Dateien hinzu: Die GUI merkt sich Ihre letzten Verzeichnisse und Exportoptionen.
- Klicken Sie auf " PDF hinzufügen", oder
- Ziehen Sie PDF-Dateien in die Liste (Drag & Drop)
**Interaktives Menü:** ### CLI-Version
Starten Sie das interaktive Menü:
```bash ```bash
./start.sh ./start.sh
``` ```
Dann wählen Sie im Menü die gewünschte Option.
### Erweiterte Nutzung (Python-Modul)abe-Verzeichnis (optional)
4. Klicken Sie auf "📄 ICS Datei erstellen"
Die GUI merkt sich Ihre letzten Verzeichnisse für schnelleren Zugriff!
### CLI-Version
**Schnellstart:**
Beide Skripte erstellen automatisch eine Python Virtual Environment und installieren alle benötigten Abhängigkeiten. Beide Skripte erstellen automatisch eine Python Virtual Environment und installieren alle benötigten Abhängigkeiten.
### Manuelle Installation ### Manuelle Installation
@@ -141,6 +134,9 @@ python3 pdf_to_ics.py --input ./pdfs --output ./ics_dateien
# Ruhetage ausschließen # Ruhetage ausschließen
python3 pdf_to_ics.py --exclude-rest python3 pdf_to_ics.py --exclude-rest
# Urlaub (060) ausschließen
python3 pdf_to_ics.py --exclude-vacation
# Einzelne PDF-Datei konvertieren # Einzelne PDF-Datei konvertieren
python3 pdf_to_ics.py /pfad/zur/datei.pdf python3 pdf_to_ics.py /pfad/zur/datei.pdf
@@ -158,6 +154,7 @@ python3 pdf_to_ics.py --help
| `--input DIR` | `-i` | Eingabe-Verzeichnis mit PDF-Dateien (Standard: aktuelles Verzeichnis) | | `--input DIR` | `-i` | Eingabe-Verzeichnis mit PDF-Dateien (Standard: aktuelles Verzeichnis) |
| `--output DIR` | `-o` | Ausgabe-Verzeichnis für ICS-Dateien (Standard: Eingabe-Verzeichnis) | | `--output DIR` | `-o` | Ausgabe-Verzeichnis für ICS-Dateien (Standard: Eingabe-Verzeichnis) |
| `--exclude-rest` | `-e` | Ruhetage ausschließen (Ruhe, R56, R36, vRWF48, RWE, vR48) | | `--exclude-rest` | `-e` | Ruhetage ausschließen (Ruhe, R56, R36, vRWF48, RWE, vR48) |
| `--exclude-vacation` | `-u` | Urlaub ausschließen (060, 0060) |
| `--verbose` | `-v` | Detaillierte Ausgabe anzeigen | | `--verbose` | `-v` | Detaillierte Ausgabe anzeigen |
| `--help` | `-h` | Hilfe anzeigen | | `--help` | `-h` | Hilfe anzeigen |
@@ -233,18 +230,18 @@ Die ICS-Datei enthält ein Event für jeden Arbeitstag mit:
``` ```
ICS-Import/ ICS-Import/
├── pdf_to_ics.py # Core-Konvertierungslogik ├── pdf_to_ics.py # Core-Konvertierungslogik
├── gui.py # GUI-Version (Tkinter) ├── gui_wxpython.py # GUI-Version (wxPython)
├── menu.py # CLI-Menü ├── menu.py # CLI-Menü
├── start_gui.sh/cmd # GUI-Startskripte ├── start_gui.sh/cmd # GUI-Startskripte
├── start.sh/cmd # CLI-Startskripte ├── start.sh/cmd # CLI-Startskripte
├── README.md # Diese Datei ├── README.md # Diese Datei
└── GUI_README.md # GUI-spezifische Dokumentation └── WXPYTHON_README.md # GUI-spezifische Dokumentation
``` ```
### Technische Spezifikationen ### Technische Spezifikationen
- **Abhängigkeiten**: pdfplumber, icalendar, pytz, pypdf2, packaging - **Abhängigkeiten**: pdfplumber, icalendar, pytz, pypdf2, packaging
- **Optional für GUI**: tkinter (Python-Standard), tkinterdnd2 (Drag & Drop) - **Optional für GUI**: wxPython (native Oberfläche)
- **Python-Version**: 3.6+ - **Python-Version**: 3.6+
- **Format**: iCalendar 2.0 (RFC 5545) - **Format**: iCalendar 2.0 (RFC 5545)
- **Konfiguration**: `~/.pdf_to_ics_config.json` (GUI-Einstellungen) - **Konfiguration**: `~/.pdf_to_ics_config.json` (GUI-Einstellungen)
@@ -257,6 +254,6 @@ Dieses Tool ist zur privaten Verwendung gedacht.
## 📚 Weitere Dokumentation ## 📚 Weitere Dokumentation
- **[GUI_README.md](GUI_README.md)** - Ausführliche GUI-Dokumentation und Tkinter-Installation - **[WXPYTHON_README.md](WXPYTHON_README.md)** - Ausführliche GUI-Dokumentation und wxPython-Hinweise
- **[QUICKSTART.md](QUICKSTART.md)** - Schnellanleitung für den Import in verschiedene Kalender - **[QUICKSTART.md](QUICKSTART.md)** - Schnellanleitung für den Import in verschiedene Kalender
- **[ZUSAMMENFASSUNG.md](ZUSAMMENFASSUNG.md)** - Projekt-Übersicht und technische Details - **[ZUSAMMENFASSUNG.md](ZUSAMMENFASSUNG.md)** - Projekt-Übersicht und technische Details

View File

@@ -74,9 +74,10 @@ sudo apt install python3 python3-pip python3-venv
### Die Anwendung startet nicht ### Die Anwendung startet nicht
Prüfen Sie, ob Tkinter installiert ist: Prüfen Sie, ob wxPython-Build-Abhängigkeiten installiert sind:
```bash ```bash
sudo apt install python3-tk sudo apt-get update
sudo apt-get install -y build-essential python3-dev libgtk-3-dev libglib2.0-dev libjpeg-dev libtiff-dev libpng-dev
``` ```
## 📞 Weitere Hilfe ## 📞 Weitere Hilfe

View File

@@ -6,15 +6,15 @@ Native GUI-Lösung mit **wxPython** - funktioniert zuverlässig auf macOS 13.6,
Nach den Problemen mit BeeWare/Toga (macOS 13.7+ erforderlich) ist wxPython die perfekte Lösung: Nach den Problemen mit BeeWare/Toga (macOS 13.7+ erforderlich) ist wxPython die perfekte Lösung:
| Feature | Tkinter | BeeWare/Toga | **wxPython** | | Feature | BeeWare/Toga | **wxPython** |
|---------|---------|--------------|--------------| |---------|--------------|--------------|
| Native auf macOS | ❌ | ✅ (nur 13.7+) | ✅ **Alle Versionen** | | Native auf macOS | ✅ (nur 13.7+) | ✅ **Alle Versionen** |
| Native auf Windows | ❌ | ✅ | ✅ | | Native auf Windows | ✅ | ✅ |
| Native auf Linux | ❌ | ✅ | ✅ | | Native auf Linux | ✅ | ✅ |
| Stabilität | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | | Stabilität | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Look & Feel | Blechig | Modern | **Perfekt nativ** | | Look & Feel | Modern | **Perfekt nativ** |
| macOS 13.6 Support | ✅ | ❌ | ✅ | | macOS 13.6 Support | ❌ | ✅ |
| Installation | Einfach | Kompliziert | Einfach | | Installation | Kompliziert | Einfach |
--- ---
@@ -58,6 +58,7 @@ Die App startet sofort mit **nativen macOS-Widgets**! 🎉
3. **Exportoptionen** 3. **Exportoptionen**
- Checkbox für "Ruhetage ausschließen" - Checkbox für "Ruhetage ausschließen"
- Checkbox für "Urlaub ausschließen (060, 0060)"
- Speichert Einstellung persistent - Speichert Einstellung persistent
4. **Konvertierung** 4. **Konvertierung**
@@ -100,6 +101,7 @@ Die App startet sofort mit **nativen macOS-Widgets**! 🎉
│ [/Users/sebastian/Documents ] 📁 │ ← Native TextCtrl │ [/Users/sebastian/Documents ] 📁 │ ← Native TextCtrl
├─────────────────────────────────────┤ ├─────────────────────────────────────┤
│ ☐ Ruhetage ausschließen │ ← Native CheckBox │ ☐ Ruhetage ausschließen │ ← Native CheckBox
│ ☐ Urlaub ausschließen (060) │ ← Native CheckBox
├─────────────────────────────────────┤ ├─────────────────────────────────────┤
│ Status: │ │ Status: │
│ ┌─────────────────────────────────┐ │ │ ┌─────────────────────────────────┐ │
@@ -115,24 +117,16 @@ Die App startet sofort mit **nativen macOS-Widgets**! 🎉
--- ---
## 🔧 Unterschiede zu Tkinter ## 🔧 wxPython APIs im Überblick
### 1. Event Handling ### 1. Event Handling
```python ```python
# Tkinter
btn = tk.Button(text="Klick", command=self.on_click)
# wxPython
btn = wx.Button(panel, label="Klick") btn = wx.Button(panel, label="Klick")
btn.Bind(wx.EVT_BUTTON, self.on_click) btn.Bind(wx.EVT_BUTTON, self.on_click)
``` ```
### 2. Layout ### 2. Layout
```python ```python
# Tkinter
frame.pack(fill=tk.BOTH, expand=True)
# wxPython
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(widget, 1, wx.EXPAND) sizer.Add(widget, 1, wx.EXPAND)
panel.SetSizer(sizer) panel.SetSizer(sizer)
@@ -140,19 +134,11 @@ panel.SetSizer(sizer)
### 3. Threading + UI ### 3. Threading + UI
```python ```python
# Tkinter
self.log_text.insert(tk.END, "Message\n")
# wxPython
wx.CallAfter(self.log_text.AppendText, "Message\n") wx.CallAfter(self.log_text.AppendText, "Message\n")
``` ```
### 4. Dialog ### 4. Dialog
```python ```python
# Tkinter
filedialog.askopenfilenames(...)
# wxPython
with wx.FileDialog(...) as dialog: with wx.FileDialog(...) as dialog:
if dialog.ShowModal() == wx.ID_CANCEL: if dialog.ShowModal() == wx.ID_CANCEL:
return return
@@ -177,7 +163,7 @@ self.pdf_listbox = wx.ListBox(panel, style=wx.LB_EXTENDED)
``` ```
### ✅ Bessere Performance ### ✅ Bessere Performance
- Schnellere Rendering als Tkinter - Schnelle native Darstellung
- Native Controls = weniger CPU - Native Controls = weniger CPU
### ✅ Moderne Features ### ✅ Moderne Features
@@ -211,6 +197,7 @@ python3 gui_wxpython.py
- [ ] "Alle entfernen" funktioniert - [ ] "Alle entfernen" funktioniert
- [ ] "Durchsuchen" für Ausgabe-Verzeichnis funktioniert - [ ] "Durchsuchen" für Ausgabe-Verzeichnis funktioniert
- [ ] Checkbox "Ruhetage" funktioniert - [ ] Checkbox "Ruhetage" funktioniert
- [ ] Checkbox "Urlaub ausschließen (060)" funktioniert
- [ ] "ICS Datei erstellen" startet Konvertierung - [ ] "ICS Datei erstellen" startet Konvertierung
- [ ] Log zeigt Status in Echtzeit - [ ] Log zeigt Status in Echtzeit
- [ ] Nach Konvertierung: Erfolgs-Dialog - [ ] Nach Konvertierung: Erfolgs-Dialog
@@ -220,7 +207,7 @@ python3 gui_wxpython.py
--- ---
## 📈 Migration von Tkinter ## 📈 Migration auf wxPython
### Was blieb gleich: ### Was blieb gleich:
- Threading-Logik - Threading-Logik
@@ -236,25 +223,22 @@ python3 gui_wxpython.py
- ✅ Native Dialoge (About, File, Directory) - ✅ Native Dialoge (About, File, Directory)
- ✅ Thread-sicheres UI-Update mit `wx.CallAfter` - ✅ Thread-sicheres UI-Update mit `wx.CallAfter`
### Status
- ✅ GUI-Start erfolgt über `start_gui.sh` / `start_gui.cmd`
- ✅ GUI-Einstiegspunkt ist `gui_wxpython.py`
--- ---
## 🚀 Nächste Schritte (optional) ## 🚀 Nächste Schritte (optional)
### 1. Ersetzt Tkinter vollständig ### 1. Packaging mit PyInstaller
Wenn wxPython gut funktioniert, können wir:
```bash
mv gui.py gui_tkinter_old.py
mv gui_wxpython.py gui.py
```
### 2. Packaging mit PyInstaller
```bash ```bash
pip install pyinstaller pip install pyinstaller
pyinstaller --onefile --windowed gui_wxpython.py pyinstaller --onefile --windowed gui_wxpython.py
``` ```
→ Erstellt `.app` Bundle für macOS! → Erstellt `.app` Bundle für macOS!
### 3. Weitere Features ### 2. Weitere Features
- Icon hinzufügen - Icon hinzufügen
- Statusbar mit Progress - Statusbar mit Progress
- Toolbar mit Icons - Toolbar mit Icons
@@ -263,19 +247,18 @@ pyinstaller --onefile --windowed gui_wxpython.py
--- ---
## 🆚 Vergleich: Tkinter vs wxPython ## 🆚 Plattformstatus
| Kriterium | Tkinter | wxPython | | Kriterium | wxPython |
|-----------|---------|----------| |-----------|----------|
| **Look auf macOS** | ❌ Alt/blechig |**Perfekt nativ** | | **Look auf macOS** | ✅ **Perfekt nativ** |
| **Menüleiste** | ⚠️ Popup-Menü |**Native MenuBar** | | **Menüleiste** | ✅ **Native MenuBar** |
| **File Dialoge** | ⚠️ Ok |**Perfekt nativ** | | **File Dialoge** | ✅ **Perfekt nativ** |
| **Thread-Safety** | ⚠️ Kompliziert |**wx.CallAfter** | | **Thread-Safety** | ✅ **wx.CallAfter** |
| **Installation** | ✅ Built-in | ⚠️ Pip install | | **Installation** | ⚠️ Pip install |
| **Bundle-Größe** | ✅ Klein | ⚠️ Größer (~20MB) | | **Bundle-Größe** | ⚠️ Größer (~20MB) |
| **Entwicklungszeit** | ✅ Schnell | ⚠️ Etwas länger | | **macOS 13.6 Support** | ✅ **Ja!** |
| **macOS 13.6 Support** | ✅ Ja | ✅ **Ja!** | | **Dark Mode** | ✅ **Automatisch** |
| **Dark Mode** | ❌ | ✅ **Automatisch** |
--- ---
@@ -296,7 +279,7 @@ pyinstaller --onefile --windowed gui_wxpython.py
2. ✅ Perfekter nativer Look & Feel 2. ✅ Perfekter nativer Look & Feel
3. ✅ Keine Versionskonflikte 3. ✅ Keine Versionskonflikte
4. ✅ Stabil und production-ready 4. ✅ Stabil und production-ready
5. ✅ Einfache Migration von Tkinter 5. ✅ Einheitlicher GUI-Stack mit wxPython
6. ✅ Alle Features vollständig implementiert 6. ✅ Alle Features vollständig implementiert
**Status:** 🎉 **PRODUCTION READY!** **Status:** 🎉 **PRODUCTION READY!**

View File

@@ -162,6 +162,22 @@ Jedes Event in der ICS-Datei:
--- ---
## 📝 Changelog (März 2026)
### v1.2.0
- GUI-Technik vollständig auf **wxPython** umgestellt
- Standard-Startpfade auf `gui_wxpython.py` angepasst (`start_gui.sh`, `start_gui.cmd`, `install.sh`)
- Linux-Hinweise für wxPython-Build-Abhängigkeiten ergänzt
- Alte Tkinter-GUI-Datei `gui.py` entfernt
- Neue Exportoption ergänzt: **Urlaub ausschließen (060/0060)**
- GUI: zusätzliche Checkbox
- CLI: `--exclude-vacation` / `-u`
- Core-Logik: Urlaubseinträge werden optional nicht in ICS exportiert
- Dokumentation konsolidiert und auf wxPython-only aktualisiert
---
## 📞 Support ## 📞 Support
Für detaillierte Informationen: Für detaillierte Informationen:
@@ -175,5 +191,5 @@ Für detaillierte Informationen:
Ihr System ist bereit. Viel Erfolg mit der Dienstplan-Verwaltung! 📅✨ Ihr System ist bereit. Viel Erfolg mit der Dienstplan-Verwaltung! 📅✨
**Letzte Änderung:** 23. Februar 2026 **Letzte Änderung:** 2. März 2026
**Status:** ✅ Einsatzbereit **Status:** ✅ Einsatzbereit

739
gui.py
View File

@@ -1,739 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
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
import re
import json
import webbrowser
from pdf_to_ics import extract_dienstplan_data, create_ics_from_dienstplan
from update_checker import check_for_updates, get_current_version
# 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):
self.root = root
self.root.title("PDF zu ICS Konverter - Dienstplan Importer")
self.root.geometry("800x600")
self.root.minsize(700, 500)
# Lade gespeicherte Einstellungen
self.config = self.load_config()
# Variablen
self.pdf_files = []
# Erstelle Menüleiste
self.create_menu()
# 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()))
# Exportoptionen
self.exclude_rest = tk.BooleanVar(value=self.config.get('exclude_rest', False))
# UI erstellen
self.create_widgets()
# Drag & Drop einrichten
self.setup_drag_and_drop()
# Update-Prüfung im Hintergrund starten
update_thread = threading.Thread(target=self.check_for_updates_background, daemon=True)
update_thread.start()
# Speichere Konfiguration beim Schließen
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
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)
# 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))
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)
# Exportoptionen
options_frame = tk.Frame(content_frame)
options_frame.pack(fill=tk.X, pady=(10, 5))
exclude_rest_check = tk.Checkbutton(
options_frame,
text="🧘 Ruhetage ausschließen - (Ruhe, R56, R36, vRWF48, RWE, vR48)",
variable=self.exclude_rest,
font=("Arial", 10)
)
exclude_rest_check.pack(anchor=tk.W)
# 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)
# Fortschrittsbalken
self.progress = ttk.Progressbar(
content_frame,
mode='determinate',
length=300
)
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}")
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 create_menu(self):
"""Erstelle die Menüleiste"""
menubar = tk.Menu(self.root)
self.root.config(menu=menubar)
# Hilfe-Menü
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Hilfe", menu=help_menu)
help_menu.add_command(label="PDF-Export auf Android (iPD)", command=self.show_android_export_guide)
help_menu.add_separator()
help_menu.add_command(label="Über dieses Programm", command=self.show_about_dialog)
help_menu.add_separator()
help_menu.add_command(label="Beenden", command=self.on_closing)
def show_android_export_guide(self):
"""Zeige Anleitung für PDF-Export aus Android App (iPD)"""
guide_window = tk.Toplevel(self.root)
guide_window.title("PDF-Export auf Android (iPD)")
guide_window.geometry("550x550")
guide_window.resizable(False, False)
# Zentriere das Fenster
guide_window.transient(self.root)
guide_window.grab_set()
# Header
header = tk.Label(
guide_window,
text="PDF-Export aus iPD",
font=("Arial", 16, "bold"),
bg="#2c3e50",
fg="white",
pady=15
)
header.pack(fill=tk.X)
# Content Frame
content = tk.Frame(guide_window, padx=20, pady=20)
content.pack(fill=tk.BOTH, expand=True)
# Anleitung-Text
guide_text = tk.Text(
content,
height=20,
font=("Courier", 9),
fg="#34495e",
wrap=tk.WORD,
relief=tk.FLAT,
bg="#f8f9fa"
)
guide_content = """1. Öffne die iPD App auf deinem Android-Gerät
2. Öffne einen Dienstplan
3. Wähle den gewünschten Monat aus
4. Tippe auf das PDF-Symbol
(rechts oben, links neben dem 3-Punkte-Menü)
5. Tippe auf "Datei herunterladen"
(rechts oben, neben Drucker-Button)
6. Wähle "Im Arbeitsprofil speichern"
7. Sende die PDF-Datei als E-Mail-Anhang
an deine private E-Mailadresse
8. Transferiere die PDF-Datei auf deinen Computer
9. Öffne diese Anwendung und füge die PDF ein
10. Klicke "ICS Datei erstellen"
11. Importiere die ICS-Datei in deinen Kalender
✓ Fertig!"""
guide_text.insert(tk.END, guide_content)
guide_text.config(state=tk.DISABLED)
guide_text.pack(fill=tk.BOTH, expand=True)
# Button-Frame
button_frame = tk.Frame(content)
button_frame.pack(fill=tk.X, pady=(15, 0))
# Online-Link Button
online_btn = tk.Button(
button_frame,
text="📖 Detaillierte Anleitung online",
command=lambda: webbrowser.open("https://git.file-archive.de/webfarben/pdf_to_ics"),
bg="#3498db",
fg="white",
font=("Arial", 10, "bold"),
padx=20,
pady=10,
cursor="hand2"
)
online_btn.pack(side=tk.LEFT, padx=(0, 10))
# Close Button
close_btn = tk.Button(
button_frame,
text="Schließen",
command=guide_window.destroy,
bg="#95a5a6",
fg="white",
font=("Arial", 10, "bold"),
padx=20,
pady=10,
cursor="hand2"
)
close_btn.pack(side=tk.LEFT)
def show_about_dialog(self):
"""Zeige About-Dialog mit Programminformationen"""
about_window = tk.Toplevel(self.root)
about_window.title("Über dieses Programm")
about_window.geometry("500x400")
about_window.resizable(False, False)
# Zentriere das Fenster
about_window.transient(self.root)
about_window.grab_set()
# Header
header = tk.Label(
about_window,
text="PDF zu ICS Konverter",
font=("Arial", 16, "bold"),
bg="#2c3e50",
fg="white",
pady=15
)
header.pack(fill=tk.X)
# Content Frame
content = tk.Frame(about_window, padx=20, pady=20)
content.pack(fill=tk.BOTH, expand=False)
# Version
version = get_current_version()
version_label = tk.Label(
content,
text=f"Version {version}",
font=("Arial", 11, "bold"),
fg="#2c3e50"
)
version_label.pack(anchor=tk.W, pady=(0, 15))
# Info-Texte
info_texts = [
("Firma:", "Webfarben"),
("Programmierer:", "Sebastian Köhler"),
("Kontakt:", "kontakt@webfarben.de"),
]
for label, value in info_texts:
frame = tk.Frame(content)
frame.pack(anchor=tk.W, pady=3, fill=tk.X)
label_widget = tk.Label(
frame,
text=label,
font=("Arial", 10, "bold"),
width=15,
anchor=tk.W
)
label_widget.pack(side=tk.LEFT)
value_widget = tk.Label(
frame,
text=value,
font=("Arial", 10),
fg="#34495e"
)
value_widget.pack(side=tk.LEFT, padx=(5, 0))
# Git Repository mit Link
repo_frame = tk.Frame(content)
repo_frame.pack(anchor=tk.W, pady=(15, 0), fill=tk.X)
repo_label = tk.Label(
repo_frame,
text="Repository:",
font=("Arial", 10, "bold"),
width=15,
anchor=tk.W
)
repo_label.pack(side=tk.LEFT)
repo_url = "https://git.file-archive.de/webfarben/pdf_to_ics.git"
repo_link = tk.Label(
repo_frame,
text=repo_url,
font=("Arial", 10, "underline"),
fg="#3498db",
cursor="hand2"
)
repo_link.pack(side=tk.LEFT, padx=(5, 0))
repo_link.bind("<Button-1>", lambda e: webbrowser.open(repo_url))
# Beschreibung
desc_frame = tk.Frame(content)
desc_frame.pack(anchor=tk.W, pady=(20, 0), fill=tk.BOTH, expand=False)
desc_text = tk.Text(
desc_frame,
height=4,
font=("Arial", 9),
fg="#34495e",
wrap=tk.WORD,
relief=tk.FLAT,
bg=about_window.cget("bg")
)
desc_text.insert(tk.END,
"Ein Programm zur Konvertierung von Dienstplan-PDFs "
"zu ICS-Kalenderdateien für einfaches Importieren "
"in Kalenderprogramme.")
desc_text.config(state=tk.DISABLED)
desc_text.pack(fill=tk.BOTH, expand=False)
# Close Button
close_btn = tk.Button(
about_window,
text="Schließen",
command=about_window.destroy,
bg="#3498db",
fg="white",
font=("Arial", 10, "bold"),
padx=50,
pady=12,
cursor="hand2"
)
close_btn.pack(pady=(10, 15), fill=tk.X, padx=20)
def save_config(self):
"""Speichere Konfiguration"""
try:
config = {
'last_output_dir': self.output_dir.get(),
'last_pdf_dir': self.last_pdf_dir,
'exclude_rest': self.exclude_rest.get()
}
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"""
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"""
# 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", "*.*")],
initialdir=initial_dir
)
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:
# 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):
"""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"""
# 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=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):
"""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), exclude_rest=self.exclude_rest.get())
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 check_for_updates_background(self):
"""Prüfe auf Updates im Hintergrund"""
try:
update_available, new_version, download_url = check_for_updates()
if update_available:
# Zeige Update-Dialog auf dem Main-Thread
self.root.after(0, self.show_update_dialog, new_version, download_url)
except Exception as e:
# Stille Fehler ignorieren, damit GUI nicht beeinflusst wird
pass
def show_update_dialog(self, new_version, download_url):
"""Zeige Update-Dialog"""
current_version = get_current_version()
response = messagebox.showinfo(
"Update verfügbar",
f"Eine neue Version ist verfügbar!\n\n"
f"Aktuelle Version: v{current_version}\n"
f"Neue Version: v{new_version}\n\n"
f"Möchten Sie die neue Version herunterladen?"
)
if response == 'ok': # Dialog mit OK-Button
import webbrowser
webbrowser.open(download_url)
def main():
"""Hauptfunktion"""
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()
if __name__ == '__main__':
main()

View File

@@ -6,7 +6,7 @@ Native Benutzeroberfläche für macOS, Windows, Linux
""" """
import wx import wx
import wx.lib.scrolledpanel as scrolled import wx.adv
from pathlib import Path from pathlib import Path
import threading import threading
import json import json
@@ -100,7 +100,8 @@ class PDFtoICSFrame(wx.Frame):
config = { config = {
'last_output_dir': self.output_dir, 'last_output_dir': self.output_dir,
'last_pdf_dir': self.last_pdf_dir, 'last_pdf_dir': self.last_pdf_dir,
'exclude_rest': self.exclude_rest_checkbox.GetValue() 'exclude_rest': self.exclude_rest_checkbox.GetValue(),
'exclude_vacation': self.exclude_vacation_checkbox.GetValue()
} }
with open(CONFIG_FILE, 'w') as f: with open(CONFIG_FILE, 'w') as f:
json.dump(config, f, indent=2) json.dump(config, f, indent=2)
@@ -202,6 +203,13 @@ class PDFtoICSFrame(wx.Frame):
self.exclude_rest_checkbox.SetValue(self.config.get('exclude_rest', False)) self.exclude_rest_checkbox.SetValue(self.config.get('exclude_rest', False))
content_sizer.Add(self.exclude_rest_checkbox, 0, wx.ALL, 10) content_sizer.Add(self.exclude_rest_checkbox, 0, wx.ALL, 10)
self.exclude_vacation_checkbox = wx.CheckBox(
panel,
label='🏖️ Urlaub ausschließen (060, 0060)'
)
self.exclude_vacation_checkbox.SetValue(self.config.get('exclude_vacation', False))
content_sizer.Add(self.exclude_vacation_checkbox, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# Log-Bereich # Log-Bereich
log_label = wx.StaticText(panel, label='Status:') log_label = wx.StaticText(panel, label='Status:')
log_label.SetFont(pdf_font) log_label.SetFont(pdf_font)
@@ -351,7 +359,8 @@ class PDFtoICSFrame(wx.Frame):
create_ics_from_dienstplan( create_ics_from_dienstplan(
dienstplan, dienstplan,
str(ics_path), str(ics_path),
exclude_rest=self.exclude_rest_checkbox.GetValue() exclude_rest=self.exclude_rest_checkbox.GetValue(),
exclude_vacation=self.exclude_vacation_checkbox.GetValue()
) )
self.log(f" ✓ ICS erstellt: {ics_filename}") self.log(f" ✓ ICS erstellt: {ics_filename}")

View File

@@ -96,40 +96,6 @@ else
print_success "venv ist bereits installiert" print_success "venv ist bereits installiert"
fi fi
# Prüfe und installiere Tkinter wenn nötig
print_step "Prüfe Tkinter-Installation..."
if ! python3 -c "import tkinter" 2>/dev/null; then
print_warning "Tkinter ist nicht installiert. Installation wird versucht..."
# Erkenne Distribution
if [ -f /etc/debian_version ]; then
echo "Debian/Ubuntu erkannt. Installiere python3-tk..."
if command -v sudo &> /dev/null; then
sudo apt-get update && sudo apt-get install -y python3-tk
else
print_error "sudo nicht verfügbar. Bitte installieren Sie python3-tk manuell:"
echo " apt-get install python3-tk"
exit 1
fi
elif [ -f /etc/fedora-release ]; then
echo "Fedora erkannt. Installiere python3-tkinter..."
sudo dnf install -y python3-tkinter
elif [ -f /etc/arch-release ]; then
echo "Arch Linux erkannt. Installiere tk..."
sudo pacman -S --noconfirm tk
else
print_warning "Distribution nicht erkannt. Bitte installieren Sie Tkinter manuell."
echo "Möchten Sie trotzdem fortfahren? (y/n)"
read -r response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
exit 1
fi
fi
print_success "Tkinter installiert"
else
print_success "Tkinter ist bereits installiert"
fi
# Erstelle Installationsverzeichnis # Erstelle Installationsverzeichnis
print_step "Erstelle Installationsverzeichnis..." print_step "Erstelle Installationsverzeichnis..."
mkdir -p "$INSTALL_DIR" mkdir -p "$INSTALL_DIR"
@@ -151,7 +117,19 @@ print_success "Virtual Environment erstellt"
print_step "Installiere Python-Abhängigkeiten..." print_step "Installiere Python-Abhängigkeiten..."
.venv/bin/pip install -q --upgrade pip .venv/bin/pip install -q --upgrade pip
.venv/bin/pip install -q pdfplumber icalendar pypdf2 pytz packaging .venv/bin/pip install -q pdfplumber icalendar pypdf2 pytz packaging
.venv/bin/pip install -q tkinterdnd2 2>/dev/null || print_warning "tkinterdnd2 optional nicht installiert (kein Problem)"
print_step "Installiere wxPython..."
if .venv/bin/pip install wxPython; then
print_success "wxPython installiert"
else
print_error "wxPython konnte nicht installiert werden."
print_warning "Auf Linux fehlen ggf. Build-Abhängigkeiten. Unter Debian/Ubuntu/Mint oft hilfreich:"
echo " sudo apt-get update"
echo " sudo apt-get install -y build-essential python3-dev libgtk-3-dev libglib2.0-dev libjpeg-dev libtiff-dev libpng-dev"
echo "Danach die Installation erneut starten: ./install.sh"
exit 1
fi
print_success "Abhängigkeiten installiert" print_success "Abhängigkeiten installiert"
# Erstelle Launcher-Script # Erstelle Launcher-Script
@@ -161,7 +139,7 @@ cat > "$LAUNCHER" << 'EOF'
# PDF zu ICS Konverter Launcher # PDF zu ICS Konverter Launcher
INSTALL_DIR="$HOME/.local/share/pdf-to-ics" INSTALL_DIR="$HOME/.local/share/pdf-to-ics"
cd "$INSTALL_DIR" cd "$INSTALL_DIR"
exec .venv/bin/python gui.py exec .venv/bin/python gui_wxpython.py
EOF EOF
chmod +x "$LAUNCHER" chmod +x "$LAUNCHER"
print_success "Launcher erstellt: $LAUNCHER" print_success "Launcher erstellt: $LAUNCHER"

View File

@@ -154,7 +154,7 @@ def parse_dienstplan_table(table, month_start_str):
return events return events
def create_ics_from_dienstplan(dienstplan, output_path=None, exclude_rest=False): def create_ics_from_dienstplan(dienstplan, output_path=None, exclude_rest=False, exclude_vacation=False):
""" """
Erstellt eine ICS-Datei aus den Dienstplan-Daten Erstellt eine ICS-Datei aus den Dienstplan-Daten
@@ -162,6 +162,7 @@ def create_ics_from_dienstplan(dienstplan, output_path=None, exclude_rest=False)
dienstplan: Dictionary mit Dienstplan-Daten dienstplan: Dictionary mit Dienstplan-Daten
output_path: Pfad für Output-Datei output_path: Pfad für Output-Datei
exclude_rest: Wenn True, werden Ruhepausen nicht exportiert exclude_rest: Wenn True, werden Ruhepausen nicht exportiert
exclude_vacation: Wenn True, wird Urlaub (060/0060) nicht exportiert
""" """
# Erstelle Calendar # Erstelle Calendar
cal = Calendar() cal = Calendar()
@@ -182,12 +183,13 @@ def create_ics_from_dienstplan(dienstplan, output_path=None, exclude_rest=False)
continue continue
service_type = event_data['service'] service_type = event_data['service']
normalized_service_type = service_type.lstrip('0') or '0'
event = Event() event = Event()
# Titel - nur den Dienstart # Titel - nur den Dienstart
# Spezielle Service-Typen mit aussagekräftigen Namen # Spezielle Service-Typen mit aussagekräftigen Namen
if service_type == '0060': if normalized_service_type == '60':
title = "Urlaub" title = "Urlaub"
elif service_type in rest_types: elif service_type in rest_types:
title = "Ruhe" title = "Ruhe"
@@ -198,6 +200,10 @@ def create_ics_from_dienstplan(dienstplan, output_path=None, exclude_rest=False)
if exclude_rest and title == "Ruhe": if exclude_rest and title == "Ruhe":
continue continue
# Überspringe Urlaub wenn gewünscht (060/0060)
if exclude_vacation and title == "Urlaub":
continue
event.add('summary', title) event.add('summary', title)
# Beschreibung # Beschreibung
@@ -268,6 +274,7 @@ Beispiele:
python3 pdf_to_ics.py # Konvertiere alle PDFs im aktuellen Verzeichnis python3 pdf_to_ics.py # Konvertiere alle PDFs im aktuellen Verzeichnis
python3 pdf_to_ics.py --input ./pdfs --output ./ics # PDFs aus ./pdfs → ICS zu ./ics python3 pdf_to_ics.py --input ./pdfs --output ./ics # PDFs aus ./pdfs → ICS zu ./ics
python3 pdf_to_ics.py --input ./pdfs --exclude-rest # Schließe Ruhetage aus python3 pdf_to_ics.py --input ./pdfs --exclude-rest # Schließe Ruhetage aus
python3 pdf_to_ics.py --input ./pdfs --exclude-vacation # Schließe Urlaub (060) aus
python3 pdf_to_ics.py file.pdf # Konvertiere einzelne Datei python3 pdf_to_ics.py file.pdf # Konvertiere einzelne Datei
""" """
) )
@@ -297,6 +304,12 @@ Beispiele:
help='Ruhetage ausschließen (Ruhe, R56, R36, vRWF48, RWE, vR48)' help='Ruhetage ausschließen (Ruhe, R56, R36, vRWF48, RWE, vR48)'
) )
parser.add_argument(
'-u', '--exclude-vacation',
action='store_true',
help='Urlaub ausschließen (060, 0060)'
)
parser.add_argument( parser.add_argument(
'-v', '--verbose', '-v', '--verbose',
action='store_true', action='store_true',
@@ -336,6 +349,8 @@ Beispiele:
print(f"📄 PDF-Dateien gefunden: {len(pdf_files)}") print(f"📄 PDF-Dateien gefunden: {len(pdf_files)}")
if args.exclude_rest: if args.exclude_rest:
print("🧘 Ruhetage werden ausgeschlossen") print("🧘 Ruhetage werden ausgeschlossen")
if args.exclude_vacation:
print("🏖️ Urlaub (060) wird ausgeschlossen")
print() print()
success_count = 0 success_count = 0
@@ -362,7 +377,12 @@ Beispiele:
# Erstelle ICS-Datei # Erstelle ICS-Datei
ics_filename = pdf_file.stem + '.ics' ics_filename = pdf_file.stem + '.ics'
ics_path = output_dir / ics_filename ics_path = output_dir / ics_filename
create_ics_from_dienstplan(dienstplan, str(ics_path), exclude_rest=args.exclude_rest) create_ics_from_dienstplan(
dienstplan,
str(ics_path),
exclude_rest=args.exclude_rest,
exclude_vacation=args.exclude_vacation
)
print(f" ✓ ICS erstellt: {ics_path}") print(f" ✓ ICS erstellt: {ics_path}")
success_count += 1 success_count += 1

View File

@@ -23,9 +23,24 @@ if errorlevel 1 (
echo ✓ Abhängigkeiten installiert echo ✓ Abhängigkeiten installiert
) )
REM Überprüfe, ob wxPython installiert ist
.venv\Scripts\python.exe -c "import wx" 2>nul
if errorlevel 1 (
echo 📚 Installiere wxPython...
call .venv\Scripts\python.exe -m pip install -q wxPython
if errorlevel 1 (
echo.
echo ❌ wxPython konnte nicht installiert werden.
echo Bitte installieren Sie Visual C++ Build Tools und versuchen Sie es erneut.
pause
exit /b 1
)
echo ✓ wxPython installiert
)
REM Starte die GUI REM Starte die GUI
echo 🎨 Starte GUI... echo 🎨 Starte GUI...
call .venv\Scripts\pythonw.exe gui.py call .venv\Scripts\pythonw.exe gui_wxpython.py
if errorlevel 1 ( if errorlevel 1 (
echo. echo.

View File

@@ -31,8 +31,8 @@ fi
# Nutze Python aus venv # Nutze Python aus venv
PYTHON_VENV=".venv/bin/python" PYTHON_VENV=".venv/bin/python"
# Überprüfe, ob Abhängigkeiten installiert sind # Überprüfe, ob Kern-Abhängigkeiten installiert sind
if ! $PYTHON_VENV -c "import pdfplumber" 2>/dev/null; then if ! $PYTHON_VENV -c "import pdfplumber, icalendar, pypdf2, pytz, packaging" 2>/dev/null; then
echo "📚 Installiere Abhängigkeiten..." echo "📚 Installiere Abhängigkeiten..."
if $PYTHON_VENV -m pip install -q pdfplumber icalendar pypdf2 pytz packaging 2>/dev/null; then if $PYTHON_VENV -m pip install -q pdfplumber icalendar pypdf2 pytz packaging 2>/dev/null; then
@@ -53,16 +53,25 @@ if ! $PYTHON_VENV -c "import pdfplumber" 2>/dev/null; then
fi fi
fi fi
# Versuche tkinterdnd2 zu installieren (optional für Drag & Drop) # Überprüfe, ob wxPython installiert ist
if ! $PYTHON_VENV -c "import tkinterdnd2" 2>/dev/null; then if ! $PYTHON_VENV -c "import wx" 2>/dev/null; then
echo "💡 Installiere tkinterdnd2 für Drag & Drop (optional)..." echo "📚 Installiere wxPython..."
$PYTHON_VENV -m pip install -q tkinterdnd2 2>/dev/null && echo "✓ Drag & Drop aktiviert" || echo " Drag & Drop nicht verfügbar (kein Problem)" if $PYTHON_VENV -m pip install wxPython; then
echo "✓ wxPython installiert"
else
echo "❌ wxPython konnte nicht installiert werden"
echo "💡 Auf Linux fehlen ggf. Build-Abhängigkeiten. Unter Debian/Ubuntu/Mint oft hilfreich:"
echo " sudo apt-get update"
echo " sudo apt-get install -y build-essential python3-dev libgtk-3-dev libglib2.0-dev libjpeg-dev libtiff-dev libpng-dev"
echo " rm -rf .venv && ./start_gui.sh"
exit 1
fi
fi fi
# Starte die GUI # Starte die GUI
echo "🎨 Starte GUI..." echo "🎨 Starte GUI..."
if [ -f "$PYTHON_VENV" ]; then if [ -f "$PYTHON_VENV" ]; then
$PYTHON_VENV gui.py $PYTHON_VENV gui_wxpython.py
else else
echo "❌ Fehler: Python-Umgebung ist beschädigt" echo "❌ Fehler: Python-Umgebung ist beschädigt"
echo "📁 Bitte löschen Sie das .venv Verzeichnis und versuchen Sie erneut:" echo "📁 Bitte löschen Sie das .venv Verzeichnis und versuchen Sie erneut:"

View File

@@ -1 +1 @@
1.1.0 1.2.0