323 lines
12 KiB
Python
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() |