- Convert about dialog from messagebox to custom Toplevel window - Improve text formatting and readability with proper spacing - Make Git repository link clickable (opens in browser with webbrowser module) - Better layout with aligned labels and structured information display - Add close button for better user experience
637 lines
21 KiB
Python
637 lines
21 KiB
Python
#!/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
|
||
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="Über dieses Programm", command=self.show_about_dialog)
|
||
help_menu.add_separator()
|
||
help_menu.add_command(label="Beenden", command=self.on_closing)
|
||
|
||
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("500x350")
|
||
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=True)
|
||
|
||
# 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=True)
|
||
|
||
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=True)
|
||
|
||
# Close Button
|
||
close_btn = tk.Button(
|
||
about_window,
|
||
text="Schließen",
|
||
command=about_window.destroy,
|
||
bg="#3498db",
|
||
fg="white",
|
||
font=("Arial", 10, "bold"),
|
||
padx=30,
|
||
pady=8,
|
||
cursor="hand2"
|
||
)
|
||
close_btn.pack(pady=(0, 15))
|
||
|
||
|
||
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()
|