Emirat/Launcher/app_launcher.py
2025-03-18 23:15:29 +01:00

323 lines
12 KiB
Python

"""
Hauptklasse für den Anwendungslauncher, der mehrere Anwendungen in Tabs verwaltet.
"""
import os
import sys
import importlib
import importlib.util
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import traceback
import logging
# Logger für den Launcher
logger = logging.getLogger("emirat.launcher")
class ApplicationLauncher(tk.Tk):
"""
Ein Hauptfenster-Launcher, der mehrere Anwendungen in Tabs laden kann.
"""
def __init__(self):
super().__init__()
# Logger einrichten
self._setup_logging()
# Grundlegende Fensterkonfiguration
self.title("Emirat Software Suite")
self.geometry("1200x800")
self.minsize(800, 600)
# Icon laden, falls vorhanden
icon_path = os.path.join(os.path.dirname(__file__), "resources", "ritterdigital.ico")
if os.path.exists(icon_path):
self.iconbitmap(icon_path)
logger.info(f"Icon geladen: {icon_path}")
else:
logger.warning(f"Icon nicht gefunden: {icon_path}")
# Hauptstilkonfiguration
self.style = ttk.Style(self)
self.style.theme_use('clam') # Alternatives Theme: 'alt', 'default', 'classic'
# Rahmen für Inhalt einrichten
self.main_frame = ttk.Frame(self)
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# Statusleiste erstellen
self.status_bar = ttk.Frame(self)
self.status_bar.pack(fill=tk.X, side=tk.BOTTOM)
self.status_label = ttk.Label(self.status_bar, text="Bereit", anchor=tk.W)
self.status_label.pack(side=tk.LEFT, padx=5)
# Notebook (Tabs) für Anwendungen einrichten
self.notebook = ttk.Notebook(self.main_frame)
self.notebook.pack(fill=tk.BOTH, expand=True)
# Anwendungs-Frames und Instanzen speichern
self.app_frames = {}
self.app_instances = {}
# Verfügbare Anwendungen finden und laden
self.app_modules = self.discover_applications()
self.load_applications()
# Event-Binding für Tab-Wechsel
self.notebook.bind("<<NotebookTabChanged>>", self.on_tab_changed)
logger.info("Launcher initialisiert")
def _setup_logging(self):
"""Richtet das Logging-System für den Launcher ein."""
logger.setLevel(logging.INFO)
if not logger.handlers:
# Konsolenausgabe
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(console_handler)
# Datei-Logging
log_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "logs")
os.makedirs(log_dir, exist_ok=True)
file_handler = logging.FileHandler(os.path.join(log_dir, "launcher.log"))
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(file_handler)
logger.info("Launcher-Logging eingerichtet")
def discover_applications(self):
"""
Sucht im Basisverzeichnis nach Anwendungsmodulen.
Erwartet für jede Anwendung einen Ordner mit einem module_launcher.py,
das eine get_app_for_launcher-Funktion enthält.
"""
app_modules = {}
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
logger.info(f"Suche nach Anwendungen in: {base_dir}")
# Bekannte Anwendungspfade
apps_to_check = [
"Preisliste",
# Hier können weitere Anwendungen hinzugefügt werden
]
for app_name in apps_to_check:
app_dir = os.path.join(base_dir, app_name)
logger.info(f"Prüfe Anwendung {app_name} in {app_dir}")
if not os.path.isdir(app_dir):
logger.warning(f"Verzeichnis nicht gefunden: {app_dir}")
continue
# Prüfen, ob es eine module_launcher.py gibt
module_path = os.path.join(app_dir, "module_launcher.py")
if os.path.exists(module_path):
logger.info(f"Gefunden: {module_path}")
app_modules[app_name] = {
"path": module_path,
"module_name": "module_launcher",
"app_dir": app_dir
}
else:
logger.warning(f"Keine module_launcher.py gefunden in {app_dir}")
# Alternativ direkt nach main.py suchen
main_path = os.path.join(app_dir, "main.py")
if os.path.exists(main_path):
logger.info(f"Gefunden: {main_path} (als Fallback)")
app_modules[app_name] = {
"path": main_path,
"module_name": "main",
"app_dir": app_dir
}
logger.info(f"Gefundene Anwendungen: {list(app_modules.keys())}")
return app_modules
def load_applications(self):
"""
Lädt die gefundenen Anwendungen und erstellt für jede einen Tab.
"""
if not self.app_modules:
logger.warning("Keine Anwendungen gefunden")
self._show_no_apps_message()
return
for app_name, app_info in self.app_modules.items():
try:
logger.info(f"Lade Anwendung: {app_name} aus {app_info['path']}")
# Anwendungsverzeichnis zum Python-Pfad hinzufügen
if app_info["app_dir"] not in sys.path:
sys.path.insert(0, app_info["app_dir"])
logger.info(f"Verzeichnis zum Pfad hinzugefügt: {app_info['app_dir']}")
# Tab-Frame erstellen
app_frame = ttk.Frame(self.notebook)
self.app_frames[app_name] = app_frame
# Modul dynamisch laden
try:
# Verschiedene Methoden zum Laden des Moduls versuchen
module = None
# Methode 1: Direkter Import, wenn Modul bereits im Pfad ist
try:
logger.info(f"Versuche direkten Import für {app_name}")
if app_info["module_name"] == "module_launcher":
# Vollständiger Import-Pfad für module_launcher
import_path = f"{app_name}.module_launcher"
logger.info(f"Import-Pfad: {import_path}")
module = importlib.import_module(import_path)
else:
# Einfacher Import für main.py oder ähnliches
import_path = app_info["module_name"]
logger.info(f"Import-Pfad: {import_path}")
module = importlib.import_module(import_path)
except ImportError as e:
logger.warning(f"Direkter Import fehlgeschlagen: {e}")
# Methode 2: Import über spec, wenn direkter Import fehlschlägt
if module is None:
logger.info(f"Versuche Import über spec für {app_name}")
spec = importlib.util.spec_from_file_location(
app_info["module_name"],
app_info["path"]
)
module = importlib.util.module_from_spec(spec)
sys.modules[app_info["module_name"]] = module
spec.loader.exec_module(module)
# Überprüfen, ob Module geladen wurde
if module is None:
raise ImportError(f"Konnte Modul {app_info['module_name']} nicht laden")
# App-Instanz erstellen
if hasattr(module, "get_app_for_launcher"):
logger.info(f"Verwende get_app_for_launcher-Funktion für {app_name}")
app_instance = module.get_app_for_launcher(app_frame)
self.app_instances[app_name] = app_instance
self.notebook.add(app_frame, text=app_name)
self.status_label.config(text=f"Anwendung '{app_name}' geladen")
else:
logger.error(f"Keine get_app_for_launcher-Funktion in {app_name}")
self._show_error_app(app_name, app_frame,
"Die Anwendung unterstützt keine direkte Launcher-Integration")
except Exception as e:
logger.error(f"Fehler beim Laden von {app_name}: {str(e)}")
traceback.print_exc()
self._show_error_app(app_name, app_frame, f"Fehler beim Laden der Anwendung: {str(e)}")
except Exception as e:
logger.error(f"Fehler bei der Anwendungsinitialisierung von {app_name}: {str(e)}")
messagebox.showerror(
"Fehler beim Laden",
f"Anwendung '{app_name}' konnte nicht geladen werden: {str(e)}"
)
traceback.print_exc()
def _show_error_app(self, app_name, frame, error_message):
"""
Zeigt eine Fehleranzeige im Tab einer Anwendung an.
"""
for widget in frame.winfo_children():
widget.destroy()
error_frame = ttk.Frame(frame, padding=20)
error_frame.pack(fill=tk.BOTH, expand=True)
error_icon = ttk.Label(
error_frame,
text="⚠️",
font=("Arial", 24)
)
error_icon.pack(pady=(0, 10))
error_title = ttk.Label(
error_frame,
text=f"Fehler in Anwendung '{app_name}'",
font=("Arial", 14, "bold"),
foreground="red"
)
error_title.pack(pady=(0, 20))
error_label = ttk.Label(
error_frame,
text=error_message,
foreground="black",
justify=tk.LEFT,
wraplength=600
)
error_label.pack(pady=10)
# Hinweis
hint_label = ttk.Label(
error_frame,
text="Weitere Informationen finden Sie in den Logdateien.",
font=("Arial", 9, "italic")
)
hint_label.pack(pady=(20, 0))
self.notebook.add(frame, text=app_name)
logger.info(f"Fehler-Tab für {app_name} erstellt")
def _show_no_apps_message(self):
"""
Zeigt eine Meldung an, wenn keine Anwendungen gefunden wurden.
"""
info_frame = ttk.Frame(self.main_frame, padding=30)
info_frame.pack(fill=tk.BOTH, expand=True)
title = ttk.Label(
info_frame,
text="Keine Anwendungen gefunden",
font=("Arial", 16, "bold")
)
title.pack(pady=(0, 20))
message = ttk.Label(
info_frame,
text=(
"Der Launcher konnte keine Anwendungen finden, die integriert werden können.\n\n"
"Bitte stellen Sie sicher, dass alle Anwendungen korrekt installiert sind und "
"eine module_launcher.py-Datei enthalten."
),
wraplength=600,
justify=tk.CENTER
)
message.pack()
def on_tab_changed(self, event):
"""
Wird aufgerufen, wenn der Benutzer zwischen Tabs wechselt.
"""
try:
current_tab = self.notebook.select()
if not current_tab:
return
tab_index = self.notebook.index(current_tab)
tab_name = self.notebook.tab(tab_index, "text")
self.status_label.config(text=f"Aktive Anwendung: {tab_name}")
# Fokus auf die aktive Anwendung setzen
if tab_name in self.app_instances:
app = self.app_instances[tab_name]
if hasattr(app, "on_activate"):
try:
app.on_activate()
logger.info(f"on_activate aufgerufen für {tab_name}")
except Exception as e:
logger.error(f"Fehler beim Aktivieren der Anwendung {tab_name}: {str(e)}")
except Exception as e:
logger.error(f"Fehler beim Tab-Wechsel: {str(e)}", exc_info=True)
if __name__ == "__main__":
app = ApplicationLauncher()
app.mainloop()