""" 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("<>", 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()