""" Kapitel 16: Master-Abschlussprojekt – Referenz-Musterlösung 🐾🏥 ================================================================ Vollständige, fehlerfreie Musterlösung aller Klassen und Komponenten. """ from __future__ import annotations import csv import json from pathlib import Path from typing import Any, Dict, List, Optional, Union # ============================================================================== # Tkinter Import mit sicherem Fallback für Headless- & Server-Umgebungen # ============================================================================== try: import tkinter as tk from tkinter import ttk, messagebox except ImportError: class _MockWidget: def __init__(self, master=None, text="", **kwargs): self.master = master self._config = dict(kwargs) if text: self._config["text"] = text self._entry_text = str(kwargs.get("textvariable", text)) self._items: list[Any] = [] self._selection: list[int] = [] def config(self, **kwargs): self._config.update(kwargs) def configure(self, **kwargs): self.config(**kwargs) def cget(self, key): return self._config.get(key, "") def __getitem__(self, key): return self._config.get(key, "") def __setitem__(self, key, value): self._config[key] = value def grid(self, **kwargs): pass def pack(self, **kwargs): pass def get(self): return self._entry_text def set(self, val): self._entry_text = str(val) def delete(self, first=0, last=None): if hasattr(self, "_items") and self._items: self._items.clear() self._entry_text = "" def insert(self, index, string): if hasattr(self, "_items"): self._items.append(str(string)) self._entry_text = str(string) def curselection(self): return tuple(self._selection) if self._selection else (0,) if self._items else () def size(self): return len(self._items) def get_item(self, index): if 0 <= index < len(self._items): return self._items[index] return "" def invoke(self): cmd = self._config.get("command") if callable(cmd): return cmd() class _MockTk(_MockWidget): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._title = "" self._geometry = "" def title(self, title: str): self._title = title def geometry(self, geom: str): self._geometry = geom def mainloop(self): pass def destroy(self): pass class _MockStringVar: def __init__(self, value=""): self._val = str(value) def get(self): return self._val def set(self, val): self._val = str(val) class _MockBooleanVar: def __init__(self, value=False): self._val = bool(value) def get(self): return self._val def set(self, val): self._val = bool(val) class _MockMessageBox: @staticmethod def showinfo(title, message): pass @staticmethod def showwarning(title, message): pass @staticmethod def showerror(title, message): pass class _MockTkModule: Tk = _MockTk Label = _MockWidget Button = _MockWidget Entry = _MockWidget Frame = _MockWidget Listbox = _MockWidget Scrollbar = _MockWidget Checkbutton = _MockWidget StringVar = _MockStringVar BooleanVar = _MockBooleanVar OptionMenu = _MockWidget END = "end" SINGLE = "single" BOTH = "both" Y = "y" W = "w" E = "e" class _MockTtkModule: Combobox = _MockWidget LabelFrame = _MockWidget Button = _MockWidget Label = _MockWidget Entry = _MockWidget Frame = _MockWidget tk = _MockTkModule() ttk = _MockTtkModule() messagebox = _MockMessageBox() # ============================================================================== # TEIL 1: EIGENE EXCEPTION-HIERARCHIE # ============================================================================== class TierheimFehler(Exception): """Basis-Exception für alle fachlichen Fehler im Tierheim.""" pass class ValidierungsFehler(TierheimFehler): """Wird ausgelöst, wenn eingegebene Daten unzulässig sind.""" pass class TierNichtGefundenFehler(TierheimFehler): """Wird ausgelöst, wenn ein gesuchtes Tier nicht existiert.""" pass class KapazitaetUeberschrittenFehler(TierheimFehler): """Wird ausgelöst, wenn das Tierheim voll ist.""" pass # ============================================================================== # TEIL 2: DAS DATENMODELL (BASISKLASSE & KINDKLASSEN) # ============================================================================== class Tier: """ Basisklasse für alle Tiere im Tierheim. """ def __init__( self, name: str, alter: int, gewicht: float, geimpft: bool = False, hunger: int = 50, ) -> None: if not isinstance(name, str) or not name.strip(): raise ValidierungsFehler("Name darf nicht leer sein!") try: alter_val = int(alter) except (ValueError, TypeError): raise ValidierungsFehler("Alter muss eine gültige ganze Zahl sein!") if alter_val < 0: raise ValidierungsFehler("Alter darf nicht negativ sein!") try: gewicht_val = float(gewicht) except (ValueError, TypeError): raise ValidierungsFehler("Gewicht muss eine gültige Kommazahl sein!") if gewicht_val <= 0: raise ValidierungsFehler("Gewicht muss größer als 0 sein!") try: hunger_val = int(hunger) except (ValueError, TypeError): hunger_val = 50 self.name: str = name.strip() self.alter: int = alter_val self.gewicht: float = gewicht_val self.geimpft: bool = bool(geimpft) self.hunger: int = max(0, min(100, hunger_val)) def fuettern(self, futtermenge_gramm: int) -> str: """Reduziert den Hunger (20 Punkte pro 100g Futter).""" if futtermenge_gramm <= 0: return f"{self.name} hat kein Futter erhalten." reduktion = int(futtermenge_gramm * 0.2) self.hunger = max(0, self.hunger - reduktion) return f"{self.name} wurde mit {futtermenge_gramm}g gefüttert. Neuer Hunger-Level: {self.hunger}/100." def impfen(self) -> bool: """Impft das Tier (setzt geimpft=True).""" if self.geimpft: return False self.geimpft = True return True def mache_laut(self) -> str: """Polymorpher Standardlaut.""" return f"{self.name} macht ein unbestimmtes Geräusch." def get_details(self) -> str: return "Keine speziellen Merkmale" def to_dict(self) -> Dict[str, Any]: """Serialisiert das Tier in ein Dictionary.""" return { "art": "Tier", "name": self.name, "alter": self.alter, "gewicht": self.gewicht, "geimpft": self.geimpft, "hunger": self.hunger, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> Tier: """Factory-Methode zur Wiederherstellung der korrekten Tier-Klasse.""" art = data.get("art", "Tier") name = data.get("name", "Unbekannt") alter = data.get("alter", 0) gewicht = data.get("gewicht", 1.0) geimpft = data.get("geimpft", False) hunger = data.get("hunger", 50) if art == "Hund": return Hund( name=name, alter=alter, gewicht=gewicht, rasse=data.get("rasse", "Mischling"), geimpft=geimpft, hunger=hunger, gassigegangen=data.get("gassigegangen", False), ) elif art == "Katze": return Katze( name=name, alter=alter, gewicht=gewicht, stubenrein=data.get("stubenrein", True), geimpft=geimpft, hunger=hunger, kratzbaum_benutzt=data.get("kratzbaum_benutzt", False), ) elif art == "Vogel": return Vogel( name=name, alter=alter, gewicht=gewicht, spannweite_cm=data.get("spannweite_cm", 25.0), kann_sprechen=data.get("kann_sprechen", False), geimpft=geimpft, hunger=hunger, ) else: return cls( name=name, alter=alter, gewicht=gewicht, geimpft=geimpft, hunger=hunger, ) def __str__(self) -> str: impf_icon = "💉 Geimpft" if self.geimpft else "❌ Ungeimpft" return f"[{self.__class__.__name__}] {self.name} ({self.alter} Jahre, {self.gewicht:.1f} kg, Hunger: {self.hunger}/100, {impf_icon})" def __repr__(self) -> str: return f"{self.__class__.__name__}(name='{self.name}', alter={self.alter}, gewicht={self.gewicht}, geimpft={self.geimpft}, hunger={self.hunger})" def __eq__(self, other: object) -> bool: if not isinstance(other, Tier): return False return ( self.name == other.name and self.alter == other.alter and abs(self.gewicht - other.gewicht) < 0.001 and self.geimpft == other.geimpft and self.hunger == other.hunger ) class Hund(Tier): """Spezialisierte Kindklasse für Hunde.""" def __init__( self, name: str, alter: int, gewicht: float, rasse: str = "Mischling", geimpft: bool = False, hunger: int = 50, gassigegangen: bool = False, ) -> None: super().__init__(name, alter, gewicht, geimpft, hunger) if not isinstance(rasse, str) or not rasse.strip(): raise ValidierungsFehler("Rasse darf nicht leer sein!") self.rasse: str = rasse.strip() self.gassigegangen: bool = bool(gassigegangen) def gassi_gehen(self, minuten: int) -> str: self.gassigegangen = True self.hunger = min(100, self.hunger + max(0, int(minuten) // 5)) return f"{self.name} war {minuten} Minuten Gassi! 🦮" def mache_laut(self) -> str: return f"{self.name} bellt: Wuff! Wuff! 🐶" def get_details(self) -> str: status = "war Gassi" if self.gassigegangen else "muss noch raus" return f"Rasse: {self.rasse} ({status})" def to_dict(self) -> Dict[str, Any]: d = super().to_dict() d["art"] = "Hund" d["rasse"] = self.rasse d["gassigegangen"] = self.gassigegangen return d class Katze(Tier): """Spezialisierte Kindklasse für Katzen.""" def __init__( self, name: str, alter: int, gewicht: float, stubenrein: bool = True, geimpft: bool = False, hunger: int = 50, kratzbaum_benutzt: bool = False, ) -> None: super().__init__(name, alter, gewicht, geimpft, hunger) self.stubenrein: bool = bool(stubenrein) self.kratzbaum_benutzt: bool = bool(kratzbaum_benutzt) def kratzen(self) -> str: self.kratzbaum_benutzt = True return f"{self.name} wetzt die Krallen am Kratzbaum! 🐾" def mache_laut(self) -> str: return f"{self.name} schnurrt: Miau! Schnurr... 🐱" def get_details(self) -> str: sauber = "stubenrein" if self.stubenrein else "nicht stubenrein" return f"Stubenrein: {sauber}" def to_dict(self) -> Dict[str, Any]: d = super().to_dict() d["art"] = "Katze" d["stubenrein"] = self.stubenrein d["kratzbaum_benutzt"] = self.kratzbaum_benutzt return d class Vogel(Tier): """Spezialisierte Kindklasse für Vögel.""" def __init__( self, name: str, alter: int, gewicht: float, spannweite_cm: float = 25.0, kann_sprechen: bool = False, geimpft: bool = False, hunger: int = 50, ) -> None: super().__init__(name, alter, gewicht, geimpft, hunger) try: sw = float(spannweite_cm) except (ValueError, TypeError): raise ValidierungsFehler("Spannweite muss eine Zahl sein!") if sw <= 0: raise ValidierungsFehler("Spannweite muss > 0 sein!") self.spannweite_cm: float = sw self.kann_sprechen: bool = bool(kann_sprechen) def fliegen(self, runden: int) -> str: self.hunger = min(100, self.hunger + max(0, int(runden) * 2)) return f"{self.name} dreht {runden} elegante Runden in der Voliere! 🕊️" def mache_laut(self) -> str: if self.kann_sprechen: return f"{self.name} plappert: 'Hallo Mensch! Tschilp!' 🦜" return f"{self.name} zwitschert: Tschilp! Tschilp! 🐦" def get_details(self) -> str: spricht = "kann sprechen" if self.kann_sprechen else "singt Melodien" return f"Spannweite: {self.spannweite_cm:.1f} cm ({spricht})" def to_dict(self) -> Dict[str, Any]: d = super().to_dict() d["art"] = "Vogel" d["spannweite_cm"] = self.spannweite_cm d["kann_sprechen"] = self.kann_sprechen return d # ============================================================================== # TEIL 3: DIE GESCHÄFTSLOGIK & DER VERWALTER (MODEL) # ============================================================================== class Tierheim: """ Das Model des Tierheims: Verwaltet Bestände, Filter, Berechnungen und Persistenz. """ def __init__(self, name: str = "Tierheim Sonnenschein", max_kapazitaet: int = 20) -> None: if not isinstance(name, str) or not name.strip(): raise ValidierungsFehler("Tierheim-Name darf nicht leer sein!") try: kap = int(max_kapazitaet) except (ValueError, TypeError): raise ValidierungsFehler("Kapazität muss eine positive ganze Zahl sein!") if kap <= 0: raise ValidierungsFehler("Kapazität muss größer als 0 sein!") self.name: str = name.strip() self.max_kapazitaet: int = kap self.tiere: List[Tier] = [] def tier_aufnehmen(self, tier: Tier) -> None: if not isinstance(tier, Tier): raise ValidierungsFehler("Nur Tier-Objekte können aufgenommen werden!") if len(self.tiere) >= self.max_kapazitaet: raise KapazitaetUeberschrittenFehler( f"Tierheim ist voll! Maximale Kapazität von {self.max_kapazitaet} erreicht." ) self.tiere.append(tier) def tier_entlassen(self, name: str) -> Tier: suchname = name.strip().lower() for i, t in enumerate(self.tiere): if t.name.lower() == suchname: return self.tiere.pop(i) raise TierNichtGefundenFehler(f"Kein Tier mit dem Namen '{name}' gefunden!") def finde_tier(self, name: str) -> Optional[Tier]: suchname = name.strip().lower() for t in self.tiere: if t.name.lower() == suchname: return t return None def filtriere_nach_art(self, art_name: str) -> List[Tier]: if not art_name or art_name.strip().lower() == "alle": return list(self.tiere) such_art = art_name.strip().lower() return [t for t in self.tiere if t.__class__.__name__.lower() == such_art] def ungeimpfte_tiere(self) -> List[Tier]: return [t for t in self.tiere if not t.geimpft] def hungrige_tiere(self, schwellenwert: int = 50) -> List[Tier]: return [t for t in self.tiere if t.hunger >= schwellenwert] def durchschnittsalter(self) -> float: if not self.tiere: return 0.0 return sum(t.alter for t in self.tiere) / len(self.tiere) def gesamtgewicht(self) -> float: return sum(t.gewicht for t in self.tiere) def alle_fuettern(self, menge_gramm: int = 100) -> Dict[str, str]: ergebnisse = {} for t in self.tiere: ergebnisse[t.name] = t.fuettern(menge_gramm) return ergebnisse def alle_impfen(self) -> int: frisch_geimpft = 0 for t in self.tiere: if t.impfen(): frisch_geimpft += 1 return frisch_geimpft def to_dict(self) -> Dict[str, Any]: return { "name": self.name, "max_kapazitaet": self.max_kapazitaet, "tiere": [t.to_dict() for t in self.tiere], } @classmethod def from_dict(cls, data: Dict[str, Any]) -> Tierheim: heim = cls( name=data.get("name", "Tierheim Sonnenschein"), max_kapazitaet=data.get("max_kapazitaet", 20), ) for t_dict in data.get("tiere", []): heim.tier_aufnehmen(Tier.from_dict(t_dict)) return heim def speichern_json(self, dateipfad: Union[str, Path]) -> None: p = Path(dateipfad) p.parent.mkdir(parents=True, exist_ok=True) with open(p, "w", encoding="utf-8") as f: json.dump(self.to_dict(), f, indent=2, ensure_ascii=False) def laden_json(self, dateipfad: Union[str, Path]) -> None: p = Path(dateipfad) if not p.exists(): raise FileNotFoundError(f"Datei '{dateipfad}' nicht gefunden!") with open(p, "r", encoding="utf-8") as f: data = json.load(f) self.name = data.get("name", self.name) self.max_kapazitaet = data.get("max_kapazitaet", self.max_kapazitaet) self.tiere = [Tier.from_dict(t) for t in data.get("tiere", [])] def exportiere_csv(self, dateipfad: Union[str, Path]) -> None: p = Path(dateipfad) p.parent.mkdir(parents=True, exist_ok=True) with open(p, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["Art", "Name", "Alter", "Gewicht", "Geimpft", "Hunger", "Details"]) for t in self.tiere: writer.writerow([ t.__class__.__name__, t.name, t.alter, f"{t.gewicht:.1f}", "Ja" if t.geimpft else "Nein", f"{t.hunger}%", t.get_details(), ]) def __len__(self) -> int: return len(self.tiere) def __str__(self) -> str: return f"Tierheim '{self.name}' ({len(self.tiere)}/{self.max_kapazitaet} Tiere)" # ============================================================================== # TEIL 4: DIE DESKTOP-BENUTZEROBERFLÄCHE (VIEW & CONTROLLER) # ============================================================================== class TierheimApp: """ Die grafische Benutzeroberfläche (Tkinter) nach dem MVC-Muster. """ def __init__(self, root: Any, tierheim: Optional[Tierheim] = None) -> None: self.root = root self.tierheim = tierheim if tierheim is not None else Tierheim() if hasattr(self.root, "title"): self.root.title("🐾 PetCare & Tierheim-Manager Pro") if hasattr(self.root, "geometry"): self.root.geometry("850x620") self._aktiver_filter = "Alle" self._erzeuge_widgets() self.aktualisiere_ansicht() def _erzeuge_widgets(self) -> None: """Erzeugt das vollständige GUI-Layout mit Widgets.""" # Oberer Titelbereich self.frame_header = tk.Frame(self.root, pady=6) self.frame_header.pack(fill="x", padx=10) self.label_titel = tk.Label( self.frame_header, text=f"🏥 {self.tierheim.name}", font=("Arial", 16, "bold"), fg="#4338ca", ) self.label_titel.pack() self.label_kapazitaet = tk.Label( self.frame_header, text="Belegung: 0 / 20 Tiere", font=("Arial", 10), fg="#64748b", ) self.label_kapazitaet.pack() # Hauptbereich: Split in Formular (Links) und Liste/Aktionen (Rechts) self.frame_main = tk.Frame(self.root) self.frame_main.pack(fill="both", expand=True, padx=10, pady=5) # ---------------- Formular (Links) ---------------- self.frame_form = tk.Frame(self.frame_main, relief="groove", bd=1, padx=10, pady=10) self.frame_form.pack(side="left", fill="y", padx=5) tk.Label(self.frame_form, text="➕ Neues Tier aufnehmen", font=("Arial", 11, "bold")).pack(pady=4) # Art tk.Label(self.frame_form, text="Tierart:", anchor="w").pack(fill="x", pady=(4, 0)) self.var_art = tk.StringVar(value="Hund") self.combo_art = ttk.Combobox( self.frame_form, textvariable=self.var_art, values=["Hund", "Katze", "Vogel"], state="readonly", ) self.combo_art.pack(fill="x", pady=2) # Name tk.Label(self.frame_form, text="Name:", anchor="w").pack(fill="x", pady=(4, 0)) self.var_name = tk.StringVar(value="") self.entry_name = tk.Entry(self.frame_form, textvariable=self.var_name) self.entry_name.pack(fill="x", pady=2) # Alter tk.Label(self.frame_form, text="Alter (Jahre):", anchor="w").pack(fill="x", pady=(4, 0)) self.var_alter = tk.StringVar(value="2") self.entry_alter = tk.Entry(self.frame_form, textvariable=self.var_alter) self.entry_alter.pack(fill="x", pady=2) # Gewicht tk.Label(self.frame_form, text="Gewicht (kg):", anchor="w").pack(fill="x", pady=(4, 0)) self.var_gewicht = tk.StringVar(value="10.5") self.entry_gewicht = tk.Entry(self.frame_form, textvariable=self.var_gewicht) self.entry_gewicht.pack(fill="x", pady=2) # Rasse / Extra tk.Label(self.frame_form, text="Rasse / Spannweite / Info:", anchor="w").pack(fill="x", pady=(4, 0)) self.var_extra = tk.StringVar(value="Golden Retriever") self.entry_extra = tk.Entry(self.frame_form, textvariable=self.var_extra) self.entry_extra.pack(fill="x", pady=2) # Geimpft self.var_geimpft = tk.BooleanVar(value=False) self.chk_geimpft = tk.Checkbutton(self.frame_form, text="Bereits geimpft 💉", variable=self.var_geimpft) self.chk_geimpft.pack(anchor="w", pady=4) # Button Aufnahme self.btn_aufnehmen = tk.Button( self.frame_form, text="Tier aufnehmen ➕", bg="#10b981", fg="white", font=("Arial", 10, "bold"), command=self.tier_aufnehmen_klick, ) self.btn_aufnehmen.pack(fill="x", pady=8) # ---------------- Listenbereich & Filter (Rechts) ---------------- self.frame_rechts = tk.Frame(self.frame_main) self.frame_rechts.pack(side="right", fill="both", expand=True, padx=5) # Filterleiste self.frame_filter = tk.Frame(self.frame_rechts) self.frame_filter.pack(fill="x", pady=2) tk.Label(self.frame_filter, text="Filter:", font=("Arial", 9, "bold")).pack(side="left", padx=4) for f_name, f_label in [ ("Alle", "Alle"), ("Hund", "Hunde 🐶"), ("Katze", "Katzen 🐱"), ("Vogel", "Vögel 🐦"), ("Ungeimpft", "Ungeimpft 💉"), ("Hungrig", "Hungrig 🍖"), ]: b = tk.Button( self.frame_filter, text=f_label, font=("Arial", 8), command=lambda fn=f_name: self.filter_setzen(fn), ) b.pack(side="left", padx=2) # Listbox für Tiere self.listbox_tiere = tk.Listbox(self.frame_rechts, font=("Courier", 9), height=11) self.listbox_tiere.pack(fill="both", expand=True, pady=4) # Aktionsbuttons für ausgewähltes Tier self.frame_aktionen = tk.Frame(self.frame_rechts) self.frame_aktionen.pack(fill="x", pady=3) self.btn_fuettern = tk.Button( self.frame_aktionen, text="Füttern (100g) 🍖", bg="#f59e0b", fg="white", command=self.tier_fuettern_klick, ) self.btn_fuettern.pack(side="left", fill="x", expand=True, padx=2) self.btn_impfen = tk.Button( self.frame_aktionen, text="Impfen 💉", bg="#3b82f6", fg="white", command=self.tier_impfen_klick, ) self.btn_impfen.pack(side="left", fill="x", expand=True, padx=2) self.btn_laut = tk.Button( self.frame_aktionen, text="Laut machen 🔊", bg="#8b5cf6", fg="white", command=self.tier_laut_klick, ) self.btn_laut.pack(side="left", fill="x", expand=True, padx=2) self.btn_vermitteln = tk.Button( self.frame_aktionen, text="Vermitteln 🏠", bg="#ef4444", fg="white", command=self.tier_vermitteln_klick, ) self.btn_vermitteln.pack(side="left", fill="x", expand=True, padx=2) # Massen- & Dateiaufsatz self.frame_datei = tk.Frame(self.frame_rechts) self.frame_datei.pack(fill="x", pady=3) self.btn_alle_fuettern = tk.Button( self.frame_datei, text="Alle füttern 🍲", command=self.alle_fuettern_klick, ) self.btn_alle_fuettern.pack(side="left", fill="x", expand=True, padx=2) self.btn_alle_impfen = tk.Button( self.frame_datei, text="Alle impfen 🩺", command=self.alle_impfen_klick, ) self.btn_alle_impfen.pack(side="left", fill="x", expand=True, padx=2) self.btn_speichern = tk.Button( self.frame_datei, text="JSON Speichern 💾", command=self.speichern_klick, ) self.btn_speichern.pack(side="left", fill="x", expand=True, padx=2) self.btn_laden = tk.Button( self.frame_datei, text="JSON Laden 📂", command=self.laden_klick, ) self.btn_laden.pack(side="left", fill="x", expand=True, padx=2) self.btn_csv = tk.Button( self.frame_datei, text="CSV Export 📊", command=self.csv_export_klick, ) self.btn_csv.pack(side="left", fill="x", expand=True, padx=2) # Statusleiste (Ganz unten) self.label_status = tk.Label( self.root, text="Bereit.", bd=1, relief="sunken", anchor="w", font=("Arial", 9), padx=5, pady=3, ) self.label_status.pack(side="bottom", fill="x") def filter_setzen(self, filter_name: str) -> None: """Setzt den aktiven Ansichts-Filter und frischt die Listbox auf.""" self._aktiver_filter = filter_name self.aktualisiere_ansicht() self.setze_status(f"Filter aktiv: {filter_name}", typ="info") def aktualisiere_ansicht(self) -> None: """Aktualisiert die Listbox und die Statistiken im Header.""" if hasattr(self.label_kapazitaet, "config"): self.label_kapazitaet.config( text=f"Belegung: {len(self.tierheim)} / {self.tierheim.max_kapazitaet} Tiere | " f"Ø {self.tierheim.durchschnittsalter():.1f} Jahre | Σ {self.tierheim.gesamtgewicht():.1f} kg" ) self.listbox_tiere.delete(0, tk.END) if self._aktiver_filter == "Ungeimpft": tiere_anzeige = self.tierheim.ungeimpfte_tiere() elif self._aktiver_filter == "Hungrig": tiere_anzeige = self.tierheim.hungrige_tiere() elif self._aktiver_filter in ("Hund", "Katze", "Vogel"): tiere_anzeige = self.tierheim.filtriere_nach_art(self._aktiver_filter) else: tiere_anzeige = self.tierheim.tiere for tier in tiere_anzeige: impf_symbol = "💉" if tier.geimpft else "❌" eintrag = f"{tier.__class__.__name__:5s} | {tier.name:12s} | {tier.alter:2d} J. | {tier.gewicht:5.1f} kg | Hunger: {tier.hunger:3d}% | {impf_symbol} | {tier.get_details()}" self.listbox_tiere.insert(tk.END, eintrag) def setze_status(self, nachricht: str, typ: str = "info") -> None: """Setzt Nachricht und Farb-Theme der Statusleiste.""" farben = { "info": "#1e293b", "success": "#166534", "warning": "#854d0e", "error": "#991b1b", } icon = {"info": "ℹ️ ", "success": "✅ ", "warning": "⚠️ ", "error": "❌ "}.get(typ, "") if hasattr(self.label_status, "config"): self.label_status.config(text=f"{icon}{nachricht}", fg=farben.get(typ, "#1e293b")) def get_ausgewaehltes_tier(self) -> Optional[Tier]: """Ermittelt das aktuell ausgewählte Tier.""" try: selektion = self.listbox_tiere.curselection() if not selektion: return None index = selektion[0] # Liste abhängig vom aktiven Filter ermitteln if self._aktiver_filter == "Ungeimpft": tiere_anzeige = self.tierheim.ungeimpfte_tiere() elif self._aktiver_filter == "Hungrig": tiere_anzeige = self.tierheim.hungrige_tiere() elif self._aktiver_filter in ("Hund", "Katze", "Vogel"): tiere_anzeige = self.tierheim.filtriere_nach_art(self._aktiver_filter) else: tiere_anzeige = self.tierheim.tiere if 0 <= index < len(tiere_anzeige): return tiere_anzeige[index] return None except Exception: return None def tier_aufnehmen_klick(self) -> None: """Verarbeitet das Formular und nimmt ein neues Tier auf.""" try: art = self.var_art.get().strip() name = self.var_name.get().strip() alter_str = self.var_alter.get().strip() gewicht_str = self.var_gewicht.get().strip() extra = self.var_extra.get().strip() geimpft = self.var_geimpft.get() if not name: raise ValidierungsFehler("Bitte einen Namen für das Tier eingeben!") try: alter = int(alter_str) except ValueError: raise ValidierungsFehler("Alter muss eine ganze Zahl sein!") try: gewicht = float(gewicht_str) except ValueError: raise ValidierungsFehler("Gewicht muss eine Zahl sein (z.B. 12.5)!") neues_tier: Tier if art == "Hund": neues_tier = Hund( name=name, alter=alter, gewicht=gewicht, rasse=extra if extra else "Mischling", geimpft=geimpft, ) elif art == "Katze": stubenrein = extra.lower() not in ("nein", "false", "0") neues_tier = Katze( name=name, alter=alter, gewicht=gewicht, stubenrein=stubenrein, geimpft=geimpft, ) elif art == "Vogel": try: spannweite = float(extra) if extra else 25.0 except ValueError: spannweite = 25.0 neues_tier = Vogel( name=name, alter=alter, gewicht=gewicht, spannweite_cm=spannweite, kann_sprechen=False, geimpft=geimpft, ) else: neues_tier = Tier(name=name, alter=alter, gewicht=gewicht, geimpft=geimpft) self.tierheim.tier_aufnehmen(neues_tier) self.aktualisiere_ansicht() self.setze_status(f"{neues_tier.name} ({art}) erfolgreich aufgenommen!", typ="success") # Formular zurücksetzen self.var_name.set("") except TierheimFehler as e: self.setze_status(str(e), typ="warning") except Exception as e: self.setze_status(f"Unerwarteter Fehler: {e}", typ="error") def tier_fuettern_klick(self) -> None: tier = self.get_ausgewaehltes_tier() if not tier: self.setze_status("Bitte wähle zuerst ein Tier in der Liste aus!", typ="warning") return msg = tier.fuettern(100) self.aktualisiere_ansicht() self.setze_status(msg, typ="success") def tier_impfen_klick(self) -> None: tier = self.get_ausgewaehltes_tier() if not tier: self.setze_status("Bitte wähle zuerst ein Tier in der Liste aus!", typ="warning") return frisch = tier.impfen() self.aktualisiere_ansicht() if frisch: self.setze_status(f"{tier.name} wurde erfolgreich geimpft! 💉", typ="success") else: self.setze_status(f"{tier.name} war bereits geimpft.", typ="info") def tier_laut_klick(self) -> None: tier = self.get_ausgewaehltes_tier() if not tier: self.setze_status("Bitte wähle zuerst ein Tier in der Liste aus!", typ="warning") return laut = tier.mache_laut() self.setze_status(laut, typ="info") def tier_vermitteln_klick(self) -> None: tier = self.get_ausgewaehltes_tier() if not tier: self.setze_status("Bitte wähle zuerst ein Tier in der Liste aus!", typ="warning") return try: entlassen = self.tierheim.tier_entlassen(tier.name) self.aktualisiere_ansicht() self.setze_status(f"{entlassen.name} wurde erfolgreich vermittelt! 🏠🎉", typ="success") except TierheimFehler as e: self.setze_status(str(e), typ="warning") def alle_fuettern_klick(self) -> None: if not self.tierheim.tiere: self.setze_status("Keine Tiere zum Füttern im Heim.", typ="info") return self.tierheim.alle_fuettern(100) self.aktualisiere_ansicht() self.setze_status(f"Alle {len(self.tierheim)} Tiere wurden gefüttert! 🍲", typ="success") def alle_impfen_klick(self) -> None: anzahl = self.tierheim.alle_impfen() self.aktualisiere_ansicht() self.setze_status(f"Impfaktion beendet: {anzahl} Tiere frisch geimpft! 🩺", typ="success") def speichern_klick(self, dateipfad: str = "tierheim_save.json") -> None: try: self.tierheim.speichern_json(dateipfad) self.setze_status(f"Gespeichert: {len(self.tierheim)} Tiere in '{dateipfad}'.", typ="success") except Exception as e: self.setze_status(f"Fehler beim Speichern: {e}", typ="error") def laden_klick(self, dateipfad: str = "tierheim_save.json") -> None: try: self.tierheim.laden_json(dateipfad) self.aktualisiere_ansicht() self.setze_status(f"Geladen: {len(self.tierheim)} Tiere aus '{dateipfad}'.", typ="success") except Exception as e: self.setze_status(f"Fehler beim Laden: {e}", typ="error") def csv_export_klick(self, dateipfad: str = "tierheim_export.csv") -> None: try: self.tierheim.exportiere_csv(dateipfad) self.setze_status(f"CSV-Export erfolgreich erstellt: '{dateipfad}'.", typ="success") except Exception as e: self.setze_status(f"Fehler beim CSV-Export: {e}", typ="error") # ============================================================================== # HAUPTPROGRAMM (DEMO) # ============================================================================== if __name__ == "__main__": heim = Tierheim("Tierheim Sonnenschein", max_kapazitaet=15) heim.tier_aufnehmen(Hund("Bello", 3, 14.5, rasse="Beagle", geimpft=True)) heim.tier_aufnehmen(Katze("Luna", 2, 4.2, stubenrein=True, geimpft=False)) heim.tier_aufnehmen(Vogel("Tweety", 1, 0.3, spannweite_cm=18.0, kann_sprechen=True)) root = tk.Tk() app = TierheimApp(root, heim) root.mainloop()